thumbtack 0.0.3 → 1.0.0
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 +4 -4
- data/lib/thumbtack.rb +14 -0
- data/lib/thumbtack/client.rb +28 -4
- data/lib/thumbtack/hash_to_digest.rb +22 -0
- data/lib/thumbtack/note.rb +30 -7
- data/lib/thumbtack/note_summary.rb +27 -7
- data/lib/thumbtack/post.rb +45 -10
- data/lib/thumbtack/posts.rb +7 -2
- data/lib/thumbtack/specification.rb +1 -2
- data/lib/thumbtack/suggestion.rb +6 -0
- data/lib/thumbtack/tags.rb +2 -2
- data/lib/thumbtack/types.rb +0 -2
- data/lib/thumbtack/types/date.rb +1 -4
- data/lib/thumbtack/types/date_time.rb +11 -4
- data/lib/thumbtack/types/identity.rb +6 -0
- data/lib/thumbtack/types/integer.rb +1 -3
- data/lib/thumbtack/types/length_validation.rb +27 -0
- data/lib/thumbtack/types/range_validation.rb +30 -0
- data/lib/thumbtack/types/text.rb +1 -4
- data/lib/thumbtack/types/title.rb +1 -4
- data/lib/thumbtack/version.rb +1 -1
- data/test/test_helper.rb +7 -1
- data/test/thumbtack/integration/client_test.rb +7 -2
- data/test/thumbtack/note_summary_test.rb +2 -2
- data/test/thumbtack/note_test.rb +2 -2
- data/test/thumbtack/notes_test.rb +2 -2
- data/test/thumbtack/post_test.rb +6 -6
- data/test/thumbtack/posts_test.rb +9 -9
- data/test/thumbtack/tags_test.rb +3 -3
- data/test/thumbtack/types/boolean_test.rb +2 -2
- data/test/thumbtack/types/date_test.rb +2 -2
- data/test/thumbtack/types/date_time_test.rb +7 -2
- data/test/thumbtack/types/integer_test.rb +2 -2
- data/test/thumbtack/types/md5_test.rb +2 -2
- data/test/thumbtack/types/tags_test.rb +2 -2
- data/test/thumbtack/types/text_test.rb +1 -1
- data/test/thumbtack/types/title_test.rb +1 -1
- data/test/thumbtack/types/url_test.rb +1 -1
- data/test/thumbtack/user_test.rb +2 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 223435e3e89fab503c5c9e5e42cb6debb6f29a5d
|
4
|
+
data.tar.gz: 769a2c552c39a0e87d35ef21c12afb475ab5029b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86c48c48a0008be9f3cea5d1143c42a7ea87915fd7a92459affc82823d26e355828942974ac9c7af81819998f6d6695c2dd26bb31eb225ef133f34802704c98e
|
7
|
+
data.tar.gz: 1a5f9d3a0d61918bcd5d4e546deb18dfe2808a281f96eec5e4ee6bfd41f15d3a739b925c55e609f3a338eea2ee7e94aed53fabfb55c34b77cd8a61c26787fb3b
|
data/lib/thumbtack.rb
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
# A simple Pinboard API wrapper
|
4
4
|
module Thumbtack
|
5
|
+
# Raised when the API rate limit has been reached
|
6
|
+
class RateLimitError < StandardError; end
|
7
|
+
|
8
|
+
# Raised when given an invalid parameter
|
9
|
+
class ValidationError < StandardError; end
|
10
|
+
|
11
|
+
# Raised when given an error from a Pinboard result code
|
12
|
+
#
|
13
|
+
# @see https://pinboard.in/api/#errors
|
14
|
+
class ResultError < StandardError; end
|
15
|
+
|
5
16
|
# An empty Hash to use for default options
|
6
17
|
EMPTY_HASH = {}.freeze
|
7
18
|
|
@@ -15,6 +26,8 @@ require 'net/http'
|
|
15
26
|
require 'uri'
|
16
27
|
|
17
28
|
require 'thumbtack/types'
|
29
|
+
require 'thumbtack/types/range_validation'
|
30
|
+
require 'thumbtack/types/length_validation'
|
18
31
|
require 'thumbtack/types/boolean'
|
19
32
|
require 'thumbtack/types/date'
|
20
33
|
require 'thumbtack/types/date_time'
|
@@ -31,6 +44,7 @@ require 'thumbtack/suggestion'
|
|
31
44
|
require 'thumbtack/posts'
|
32
45
|
require 'thumbtack/tags'
|
33
46
|
require 'thumbtack/user'
|
47
|
+
require 'thumbtack/hash_to_digest'
|
34
48
|
require 'thumbtack/note'
|
35
49
|
require 'thumbtack/note_summary'
|
36
50
|
require 'thumbtack/notes'
|
data/lib/thumbtack/client.rb
CHANGED
@@ -3,9 +3,6 @@
|
|
3
3
|
module Thumbtack
|
4
4
|
# Wraps each interaction with the Pinboard API
|
5
5
|
class Client
|
6
|
-
# Raised when the API rate limit has been reached
|
7
|
-
class RateLimitError < StandardError; end
|
8
|
-
|
9
6
|
# The status code for rate limited responses from the Pinboard API
|
10
7
|
TOO_MANY_REQUESTS_CODE = '429'.freeze
|
11
8
|
|
@@ -14,6 +11,9 @@ module Thumbtack
|
|
14
11
|
|
15
12
|
# Username used by the client to make authenticated requests
|
16
13
|
#
|
14
|
+
# @example
|
15
|
+
# client.username # => 'maciej'
|
16
|
+
#
|
17
17
|
# @return [String]
|
18
18
|
#
|
19
19
|
# @api public
|
@@ -21,6 +21,9 @@ module Thumbtack
|
|
21
21
|
|
22
22
|
# Token used by the client to make authenticated requests
|
23
23
|
#
|
24
|
+
# @example
|
25
|
+
# client.token # => 'C9044F4047891CEA74FE'
|
26
|
+
#
|
24
27
|
# @return [String]
|
25
28
|
#
|
26
29
|
# @api public
|
@@ -61,10 +64,31 @@ module Thumbtack
|
|
61
64
|
uri.query = URI.encode_www_form(params.merge(base_params))
|
62
65
|
|
63
66
|
response = Net::HTTP.get_response(uri)
|
64
|
-
fail
|
67
|
+
fail RateLimitError if response.code == TOO_MANY_REQUESTS_CODE
|
65
68
|
JSON.parse(response.body)
|
66
69
|
end
|
67
70
|
|
71
|
+
# Perform an action request against the Pinboard API
|
72
|
+
#
|
73
|
+
# @param [String] path
|
74
|
+
# the path to fetch from, relative to from the base Pinboard API URL
|
75
|
+
#
|
76
|
+
# @param [Hash] params
|
77
|
+
# query parameters to append to the URL
|
78
|
+
#
|
79
|
+
# @return [Hash] the response parsed from the JSON
|
80
|
+
#
|
81
|
+
# @raise [RateLimitError] if the response is rate-limited
|
82
|
+
# @raise [ResultError] if the result code isn't "done"
|
83
|
+
#
|
84
|
+
# @api private
|
85
|
+
# @see https://pinboard.in/api/#errors
|
86
|
+
def action(path, params = EMPTY_HASH)
|
87
|
+
response = get(path, params)
|
88
|
+
fail ResultError, response['result'] unless response['result'] == 'done'
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
68
92
|
# Access posts-related API calls
|
69
93
|
#
|
70
94
|
# @example
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Thumbtack
|
2
|
+
# Handles renaming the hash attribute to digest from response hashes.
|
3
|
+
#
|
4
|
+
# @api private
|
5
|
+
class HashToDigest
|
6
|
+
# Rename any attribute called hash to digest
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# HashToDigest.rename('hash' => '1234') # => { :digest => '1234' }
|
10
|
+
#
|
11
|
+
# @param [Hash{String => Object}]
|
12
|
+
# reponse hash
|
13
|
+
#
|
14
|
+
# @return [Hash{Symbol => Object}]
|
15
|
+
# a hash with any key named hash renamed to digest
|
16
|
+
def self.rename(hash)
|
17
|
+
attrs = hash.dup
|
18
|
+
digest = attrs.delete('hash')
|
19
|
+
Hash[attrs.map { |k, v| [k.to_sym, v] }].merge(digest: digest)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/thumbtack/note.rb
CHANGED
@@ -20,6 +20,9 @@ module Thumbtack
|
|
20
20
|
|
21
21
|
# The identifier for the note
|
22
22
|
#
|
23
|
+
# @example
|
24
|
+
# note.id # => '8e5d6964bb810e0050b0'
|
25
|
+
#
|
23
26
|
# @return [String]
|
24
27
|
#
|
25
28
|
# @api public
|
@@ -27,6 +30,9 @@ module Thumbtack
|
|
27
30
|
|
28
31
|
# The title of the note
|
29
32
|
#
|
33
|
+
# @example
|
34
|
+
# note.title # => 'StarCraft beta coming this week!'
|
35
|
+
#
|
30
36
|
# @return [String]
|
31
37
|
#
|
32
38
|
# @api public
|
@@ -34,20 +40,33 @@ module Thumbtack
|
|
34
40
|
|
35
41
|
# The time at which the note was created
|
36
42
|
#
|
37
|
-
# @
|
43
|
+
# @example
|
44
|
+
# note.created_at # => #<DateTime: 2014-08-13T19:53:16+00:00...
|
45
|
+
#
|
46
|
+
# @return [DateTime]
|
38
47
|
#
|
39
48
|
# @api public
|
40
|
-
|
49
|
+
def created_at
|
50
|
+
Types::DateTime.from_note_parameter(@created_at)
|
51
|
+
end
|
41
52
|
|
42
53
|
# The time at which the note was last updated
|
43
54
|
#
|
44
|
-
# @
|
55
|
+
# @example
|
56
|
+
# note.updated_at # => #<DateTime: 2014-08-13T19:53:16+00:00...
|
57
|
+
#
|
58
|
+
# @return [DateTime]
|
45
59
|
#
|
46
60
|
# @api public
|
47
|
-
|
61
|
+
def updated_at
|
62
|
+
Types::DateTime.from_note_parameter(@updated_at)
|
63
|
+
end
|
48
64
|
|
49
65
|
# 20 character hexadecimal SHA1 hash of the note text
|
50
66
|
#
|
67
|
+
# @example
|
68
|
+
# note.digest # => '0c9c30f60cadabd31415'
|
69
|
+
#
|
51
70
|
# @return [String]
|
52
71
|
#
|
53
72
|
# @api public
|
@@ -55,6 +74,9 @@ module Thumbtack
|
|
55
74
|
|
56
75
|
# The text of the note
|
57
76
|
#
|
77
|
+
# @example
|
78
|
+
# note.text # => 'It was clear that readers showing up...
|
79
|
+
#
|
58
80
|
# @return [String]
|
59
81
|
#
|
60
82
|
# @api public
|
@@ -62,6 +84,9 @@ module Thumbtack
|
|
62
84
|
|
63
85
|
# The length of the note text
|
64
86
|
#
|
87
|
+
# @example
|
88
|
+
# note.length # => 153
|
89
|
+
#
|
65
90
|
# @return [Integer]
|
66
91
|
#
|
67
92
|
# @api public
|
@@ -77,9 +102,7 @@ module Thumbtack
|
|
77
102
|
# @api private
|
78
103
|
# @see Client#get
|
79
104
|
def self.from_hash(hash)
|
80
|
-
|
81
|
-
digest = attrs.delete('hash')
|
82
|
-
new(Hash[attrs.map { |k, v| [k.to_sym, v] }].merge(digest: digest))
|
105
|
+
new(HashToDigest.rename(hash))
|
83
106
|
end
|
84
107
|
|
85
108
|
# Initialize a Note
|
@@ -20,6 +20,9 @@ module Thumbtack
|
|
20
20
|
|
21
21
|
# The identifier for the note
|
22
22
|
#
|
23
|
+
# @example
|
24
|
+
# note.id # => '8e5d6964bb810e0050b0'
|
25
|
+
#
|
23
26
|
# @return [String]
|
24
27
|
#
|
25
28
|
# @api public
|
@@ -27,6 +30,9 @@ module Thumbtack
|
|
27
30
|
|
28
31
|
# The title of the note
|
29
32
|
#
|
33
|
+
# @example
|
34
|
+
# note.title # => 'StarCraft beta coming this week!'
|
35
|
+
#
|
30
36
|
# @return [String]
|
31
37
|
#
|
32
38
|
# @api public
|
@@ -34,20 +40,33 @@ module Thumbtack
|
|
34
40
|
|
35
41
|
# The time at which the note was created
|
36
42
|
#
|
37
|
-
# @
|
43
|
+
# @example
|
44
|
+
# note.created_at # => #<DateTime: 2014-08-13T19:53:16+00:00...
|
45
|
+
#
|
46
|
+
# @return [DateTime]
|
38
47
|
#
|
39
48
|
# @api public
|
40
|
-
|
49
|
+
def created_at
|
50
|
+
Types::DateTime.from_note_parameter(@created_at)
|
51
|
+
end
|
41
52
|
|
42
53
|
# The time at which the note was last updated
|
43
54
|
#
|
44
|
-
# @
|
55
|
+
# @example
|
56
|
+
# note.updated_at # => #<DateTime: 2014-08-13T19:53:16+00:00...
|
57
|
+
#
|
58
|
+
# @return [DateTime]
|
45
59
|
#
|
46
60
|
# @api public
|
47
|
-
|
61
|
+
def updated_at
|
62
|
+
Types::DateTime.from_note_parameter(@updated_at)
|
63
|
+
end
|
48
64
|
|
49
65
|
# 20 character hexadecimal SHA1 hash of the note text
|
50
66
|
#
|
67
|
+
# @example
|
68
|
+
# note.digest # => '0c9c30f60cadabd31415'
|
69
|
+
#
|
51
70
|
# @return [String]
|
52
71
|
#
|
53
72
|
# @api public
|
@@ -55,6 +74,9 @@ module Thumbtack
|
|
55
74
|
|
56
75
|
# The length of the note text
|
57
76
|
#
|
77
|
+
# @example
|
78
|
+
# note.length # => 153
|
79
|
+
#
|
58
80
|
# @return [Integer]
|
59
81
|
#
|
60
82
|
# @api public
|
@@ -70,9 +92,7 @@ module Thumbtack
|
|
70
92
|
# @api private
|
71
93
|
# @see Client#get
|
72
94
|
def self.from_hash(hash)
|
73
|
-
|
74
|
-
digest = attrs.delete('hash')
|
75
|
-
new(Hash[attrs.map { |k, v| [k.to_sym, v] }].merge(digest: digest))
|
95
|
+
new(HashToDigest.rename(hash))
|
76
96
|
end
|
77
97
|
|
78
98
|
# Initialize a NoteSummary
|
data/lib/thumbtack/post.rb
CHANGED
@@ -13,7 +13,7 @@ module Thumbtack
|
|
13
13
|
:description,
|
14
14
|
:extended,
|
15
15
|
:meta,
|
16
|
-
:
|
16
|
+
:digest,
|
17
17
|
:time,
|
18
18
|
:shared,
|
19
19
|
:toread,
|
@@ -22,6 +22,9 @@ module Thumbtack
|
|
22
22
|
|
23
23
|
# The url of the post
|
24
24
|
#
|
25
|
+
# @example
|
26
|
+
# post.href # => 'http://pinboard.in'
|
27
|
+
#
|
25
28
|
# @return [String]
|
26
29
|
#
|
27
30
|
# @api public
|
@@ -29,6 +32,9 @@ module Thumbtack
|
|
29
32
|
|
30
33
|
# The title of the post
|
31
34
|
#
|
35
|
+
# @example
|
36
|
+
# post.description # => 'Pinboard'
|
37
|
+
#
|
32
38
|
# @return [String]
|
33
39
|
#
|
34
40
|
# @api public
|
@@ -36,12 +42,18 @@ module Thumbtack
|
|
36
42
|
|
37
43
|
# The description of the post
|
38
44
|
#
|
45
|
+
# @example
|
46
|
+
# post.extended # => 'antisocial bookmarking'
|
47
|
+
#
|
39
48
|
# @return [String]
|
40
49
|
#
|
41
50
|
# @api public
|
42
51
|
attr_reader :extended
|
43
52
|
|
44
|
-
# 32 character hexadecimal MD5 hash
|
53
|
+
# 32 character hexadecimal MD5 hash change signature for the post
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# post.meta # => '92959a96fd69146c5fe7cbde6e5720f2'
|
45
57
|
#
|
46
58
|
# @return [String]
|
47
59
|
#
|
@@ -50,38 +62,61 @@ module Thumbtack
|
|
50
62
|
|
51
63
|
# 32 character hexadecimal MD5 hash of the post URL
|
52
64
|
#
|
65
|
+
# @example
|
66
|
+
# post.digest # => '92959a96fd69146c5fe7cbde6e5720f2'
|
67
|
+
#
|
53
68
|
# @return [String]
|
54
69
|
#
|
55
70
|
# @api public
|
56
|
-
attr_reader :
|
71
|
+
attr_reader :digest
|
57
72
|
|
58
73
|
# The time at which the post was created
|
59
74
|
#
|
60
|
-
# @
|
75
|
+
# @example
|
76
|
+
# post.time # => #<DateTime: 2014-09-17T13:45:27+00:00...
|
77
|
+
#
|
78
|
+
# @return [DateTime]
|
61
79
|
#
|
62
80
|
# @api public
|
63
|
-
|
81
|
+
def time
|
82
|
+
Types::DateTime.from_parameter(@time)
|
83
|
+
end
|
64
84
|
|
65
85
|
# If true, this post is public
|
66
86
|
#
|
87
|
+
# @example
|
88
|
+
# post.shared # => true
|
89
|
+
#
|
67
90
|
# @return [Boolean]
|
68
91
|
#
|
69
92
|
# @api public
|
70
|
-
|
93
|
+
def shared
|
94
|
+
Types::Boolean.from_parameter(@shared)
|
95
|
+
end
|
71
96
|
|
72
97
|
# If true, this post is marked unread
|
73
98
|
#
|
99
|
+
# @example
|
100
|
+
# post.toread # => false
|
101
|
+
#
|
74
102
|
# @return [Boolean]
|
75
103
|
#
|
76
104
|
# @api public
|
77
|
-
|
105
|
+
def toread
|
106
|
+
Types::Boolean.from_parameter(@toread)
|
107
|
+
end
|
78
108
|
|
79
109
|
# The tags for this post, space-seperated
|
80
110
|
#
|
81
|
-
# @
|
111
|
+
# @example
|
112
|
+
# post.tags # => ['bookmarks', 'tools']
|
113
|
+
#
|
114
|
+
# @return [Array<String>]
|
82
115
|
#
|
83
116
|
# @api public
|
84
|
-
|
117
|
+
def tags
|
118
|
+
Types::Tags.from_parameter(@tags)
|
119
|
+
end
|
85
120
|
|
86
121
|
# Creates a new Post from a Hash
|
87
122
|
#
|
@@ -93,7 +128,7 @@ module Thumbtack
|
|
93
128
|
# @api private
|
94
129
|
# @see Client#get
|
95
130
|
def self.from_hash(hash)
|
96
|
-
new(
|
131
|
+
new(HashToDigest.rename(hash))
|
97
132
|
end
|
98
133
|
|
99
134
|
# Initialize a Post
|
data/lib/thumbtack/posts.rb
CHANGED
@@ -68,7 +68,7 @@ module Thumbtack
|
|
68
68
|
shared: Types::Boolean,
|
69
69
|
toread: Types::Boolean
|
70
70
|
).parameters({ url: url, description: description }.merge(options))
|
71
|
-
@client.
|
71
|
+
@client.action('/posts/add', parameters)
|
72
72
|
self
|
73
73
|
end
|
74
74
|
|
@@ -87,7 +87,7 @@ module Thumbtack
|
|
87
87
|
# @see https://pinboard.in/api/#posts_delete
|
88
88
|
def delete(url)
|
89
89
|
parameters = Specification.new(url: Types::URL).parameters(url: url)
|
90
|
-
@client.
|
90
|
+
@client.action('/posts/delete', parameters)
|
91
91
|
self
|
92
92
|
end
|
93
93
|
|
@@ -235,6 +235,11 @@ module Thumbtack
|
|
235
235
|
|
236
236
|
private
|
237
237
|
|
238
|
+
# Create Post objects from posts response
|
239
|
+
#
|
240
|
+
# @return [Array<Post>]
|
241
|
+
#
|
242
|
+
# @api private
|
238
243
|
def posts_from(response)
|
239
244
|
response.fetch('posts', EMPTY_ARRAY).map do |post_hash|
|
240
245
|
Post.from_hash(post_hash)
|
@@ -14,8 +14,7 @@ module Thumbtack
|
|
14
14
|
@type_handlers = type_handlers
|
15
15
|
end
|
16
16
|
|
17
|
-
# Validate and translate
|
18
|
-
# Pinboard-supported values
|
17
|
+
# Validate and translate client parameters to their Pinboard values
|
19
18
|
#
|
20
19
|
# @param [Hash{Symbol => Object}] arguments
|
21
20
|
# parameter names associated with their values
|
data/lib/thumbtack/suggestion.rb
CHANGED
@@ -17,6 +17,9 @@ module Thumbtack
|
|
17
17
|
|
18
18
|
# A list of popular tags for URL
|
19
19
|
#
|
20
|
+
# @example
|
21
|
+
# suggestion.popular # => ['blog', 'blogs', 'people']
|
22
|
+
#
|
20
23
|
# @return [Array<String>]
|
21
24
|
#
|
22
25
|
# @api public
|
@@ -24,6 +27,9 @@ module Thumbtack
|
|
24
27
|
|
25
28
|
# A list of recommended tags for the URL
|
26
29
|
#
|
30
|
+
# @example
|
31
|
+
# suggestion.recommended # => ['blog', 'blogs', 'writing'...
|
32
|
+
#
|
27
33
|
# @return [Array<String>]
|
28
34
|
#
|
29
35
|
# @api public
|
data/lib/thumbtack/tags.rb
CHANGED
@@ -44,7 +44,7 @@ module Thumbtack
|
|
44
44
|
# @see https://pinboard.in/api/#tags_delete
|
45
45
|
def delete(tag)
|
46
46
|
parameters = Specification.new(tag: Types::Tags).parameters(tag: tag)
|
47
|
-
@client.
|
47
|
+
@client.action('/tags/delete', parameters)
|
48
48
|
self
|
49
49
|
end
|
50
50
|
|
@@ -68,7 +68,7 @@ module Thumbtack
|
|
68
68
|
old: Types::Tags,
|
69
69
|
new: Types::Tags
|
70
70
|
).parameters(old: old, new: new)
|
71
|
-
@client.
|
71
|
+
@client.action('/tags/rename', parameters)
|
72
72
|
self
|
73
73
|
end
|
74
74
|
end
|
data/lib/thumbtack/types.rb
CHANGED
data/lib/thumbtack/types/date.rb
CHANGED
@@ -22,10 +22,7 @@ module Thumbtack
|
|
22
22
|
# @raise [Types::ValidationError]
|
23
23
|
# if the date is not between 0001-01-01 and 2100-01-01
|
24
24
|
def self.validate(value)
|
25
|
-
|
26
|
-
fail ValidationError,
|
27
|
-
"#{value} must be between 0001-01-01 and 2100-01-01"
|
28
|
-
end
|
25
|
+
RangeValidation.validate value, EARLIEST..LATEST
|
29
26
|
self
|
30
27
|
end
|
31
28
|
|
@@ -24,10 +24,7 @@ module Thumbtack
|
|
24
24
|
# @raise [Types::ValidationError]
|
25
25
|
# if the time is not between 0001-01-01 00:00:00 and 2100-01-01 00:00:00
|
26
26
|
def self.validate(value)
|
27
|
-
|
28
|
-
fail ValidationError,
|
29
|
-
"#{value} must be between 0001-01-01 and 2100-01-01"
|
30
|
-
end
|
27
|
+
RangeValidation.validate value, EARLIEST..LATEST
|
31
28
|
self
|
32
29
|
end
|
33
30
|
|
@@ -51,6 +48,16 @@ module Thumbtack
|
|
51
48
|
def self.from_parameter(parameter)
|
52
49
|
::DateTime.strptime(parameter)
|
53
50
|
end
|
51
|
+
|
52
|
+
# Convert a parameter from Pinboard's notes to a datetime value
|
53
|
+
#
|
54
|
+
# @param [String] parameter
|
55
|
+
# the time formatted yyyy-mm-dd HH:MM:SS
|
56
|
+
#
|
57
|
+
# @return [DateTime]
|
58
|
+
def self.from_note_parameter(parameter)
|
59
|
+
::DateTime.strptime(parameter, '%Y-%m-%d %H:%M:%S')
|
60
|
+
end
|
54
61
|
end
|
55
62
|
end
|
56
63
|
end
|
@@ -15,6 +15,9 @@ module Thumbtack
|
|
15
15
|
|
16
16
|
# Value is returned unconverted
|
17
17
|
#
|
18
|
+
# @param [Object] value
|
19
|
+
# the value to return
|
20
|
+
#
|
18
21
|
# @return [value]
|
19
22
|
def self.to_parameter(value)
|
20
23
|
value
|
@@ -22,6 +25,9 @@ module Thumbtack
|
|
22
25
|
|
23
26
|
# Parameter is returned unconverted
|
24
27
|
#
|
28
|
+
# @param [Object] parameter
|
29
|
+
# the parameter to return
|
30
|
+
#
|
25
31
|
# @return [parameter]
|
26
32
|
def self.from_parameter(parameter)
|
27
33
|
parameter
|
@@ -21,9 +21,7 @@ module Thumbtack
|
|
21
21
|
# @raise [Types::ValidationError]
|
22
22
|
# if the value is not between 0 and 2^32
|
23
23
|
def self.validate(value)
|
24
|
-
|
25
|
-
fail ValidationError, "#{value} must be in range 0..2^32"
|
26
|
-
end
|
24
|
+
RangeValidation.validate value, MIN..MAX
|
27
25
|
self
|
28
26
|
end
|
29
27
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Thumbtack
|
2
|
+
module Types
|
3
|
+
# Handles validation of values within a certain range
|
4
|
+
# Pinboard
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class LengthValidation
|
8
|
+
# Validate a value
|
9
|
+
#
|
10
|
+
# @param [Object] length
|
11
|
+
# the maximum length
|
12
|
+
# @param [Object] value
|
13
|
+
# the value to validate
|
14
|
+
#
|
15
|
+
# @return [undefined]
|
16
|
+
#
|
17
|
+
# @raise [Types::ValidationError]
|
18
|
+
# if the value is not between 0001-01-01 and 2100-01-01
|
19
|
+
def self.validate(value, length)
|
20
|
+
unless value.length <= length
|
21
|
+
fail ValidationError,
|
22
|
+
"#{value} cannot be greater than #{length} characters"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Thumbtack
|
2
|
+
module Types
|
3
|
+
# Handles validation of values within a certain range
|
4
|
+
# Pinboard
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class RangeValidation
|
8
|
+
# Validate a value
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# RangeValidation.validate((1..2), 3) # => ValidationError raised
|
12
|
+
#
|
13
|
+
# @param [Object] value
|
14
|
+
# the value to validate
|
15
|
+
# @param [Range] range
|
16
|
+
# the range of valid values
|
17
|
+
#
|
18
|
+
# @return [undefined]
|
19
|
+
#
|
20
|
+
# @raise [Types::ValidationError]
|
21
|
+
# if the value is not between 0001-01-01 and 2100-01-01
|
22
|
+
def self.validate(value, range)
|
23
|
+
unless range.cover?(value)
|
24
|
+
fail ValidationError,
|
25
|
+
"#{value} must be between #{range.begin} and #{range.end}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/thumbtack/types/text.rb
CHANGED
@@ -19,10 +19,7 @@ module Thumbtack
|
|
19
19
|
# @raise [Types::ValidationError]
|
20
20
|
# if the value is longer than 65536 characters
|
21
21
|
def self.validate(value)
|
22
|
-
|
23
|
-
fail ValidationError,
|
24
|
-
"#{value} cannot be greater than #{MAXIMUM_LENGTH} characters"
|
25
|
-
end
|
22
|
+
LengthValidation.validate value, MAXIMUM_LENGTH
|
26
23
|
self
|
27
24
|
end
|
28
25
|
end
|
@@ -19,10 +19,7 @@ module Thumbtack
|
|
19
19
|
# @raise [Types::ValidationError]
|
20
20
|
# if the value is longer than 255 characters
|
21
21
|
def self.validate(value)
|
22
|
-
|
23
|
-
fail ValidationError,
|
24
|
-
"#{value} cannot be greater than #{MAXIMUM_LENGTH} characters"
|
25
|
-
end
|
22
|
+
LengthValidation.validate value, MAXIMUM_LENGTH
|
26
23
|
self
|
27
24
|
end
|
28
25
|
end
|
data/lib/thumbtack/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -5,10 +5,16 @@ CodeClimate::TestReporter.start
|
|
5
5
|
require 'thumbtack'
|
6
6
|
require 'minitest/autorun'
|
7
7
|
|
8
|
-
def
|
8
|
+
def mock_client_get(url, params, response)
|
9
9
|
client = Minitest::Mock.new
|
10
10
|
client.expect(:get, response, [url, params].compact)
|
11
11
|
client
|
12
12
|
end
|
13
13
|
|
14
|
+
def mock_client_action(url, params, response)
|
15
|
+
client = Minitest::Mock.new
|
16
|
+
client.expect(:action, response, [url, params].compact)
|
17
|
+
client
|
18
|
+
end
|
19
|
+
|
14
20
|
include Thumbtack
|
@@ -17,8 +17,13 @@ module Integration
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_request_query_parameters
|
20
|
-
response = @client.get('/
|
21
|
-
assert_equal(
|
20
|
+
response = @client.get('/posts/recent', tag: 'thumbtack-test-query-params')
|
21
|
+
assert_equal([] , response['posts'])
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_action_command_method
|
25
|
+
response = @client.action('/tags/delete', tag: 'thumbtack-test-command')
|
26
|
+
assert_equal(@client, response)
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
@@ -17,8 +17,8 @@ class NoteSummaryTest < Minitest::Test
|
|
17
17
|
assert_equal '8e5d6964bb810e0050b0', note.id
|
18
18
|
assert_equal 'StarCraft beta coming this week!', note.title
|
19
19
|
assert_equal '0c9c30f60cadabd31415', note.digest
|
20
|
-
assert_equal
|
21
|
-
assert_equal
|
20
|
+
assert_equal DateTime.new(2010, 2, 11, 3, 46, 56), note.created_at
|
21
|
+
assert_equal DateTime.new(2010, 2, 11, 3, 47, 47), note.updated_at
|
22
22
|
assert_equal 19, note.length
|
23
23
|
end
|
24
24
|
end
|
data/test/thumbtack/note_test.rb
CHANGED
@@ -18,8 +18,8 @@ class NoteTest < Minitest::Test
|
|
18
18
|
assert_equal '8e5d6964bb810e0050b0', note.id
|
19
19
|
assert_equal 'StarCraft beta coming this week!', note.title
|
20
20
|
assert_equal '0c9c30f60cadabd31415', note.digest
|
21
|
-
assert_equal
|
22
|
-
assert_equal
|
21
|
+
assert_equal DateTime.new(2010, 2, 11, 3, 46, 56), note.created_at
|
22
|
+
assert_equal DateTime.new(2010, 2, 11, 3, 47, 47), note.updated_at
|
23
23
|
assert_equal 19, note.length
|
24
24
|
assert_equal 'This is a test note', note.text
|
25
25
|
end
|
@@ -4,7 +4,7 @@ require 'test_helper'
|
|
4
4
|
|
5
5
|
class NotesTest < Minitest::Test
|
6
6
|
def test_list
|
7
|
-
client =
|
7
|
+
client = mock_client_get('/notes/list',
|
8
8
|
nil,
|
9
9
|
{
|
10
10
|
'count' => 2,
|
@@ -36,7 +36,7 @@ class NotesTest < Minitest::Test
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_get
|
39
|
-
client =
|
39
|
+
client = mock_client_get('/notes/8e5d6964bb810e0050b0',
|
40
40
|
nil,
|
41
41
|
{
|
42
42
|
'id' => '8e5d6964bb810e0050b0',
|
data/test/thumbtack/post_test.rb
CHANGED
@@ -13,7 +13,7 @@ class PostTest < Minitest::Test
|
|
13
13
|
'time' => '2014-06-29T16:57:45Z',
|
14
14
|
'shared' => 'yes',
|
15
15
|
'toread' => 'no',
|
16
|
-
'tags' => 'test123'
|
16
|
+
'tags' => 'test123 another'
|
17
17
|
}
|
18
18
|
post = Post.from_hash(hash)
|
19
19
|
|
@@ -21,10 +21,10 @@ class PostTest < Minitest::Test
|
|
21
21
|
assert_equal 'example.org', post.description
|
22
22
|
assert_equal '', post.extended
|
23
23
|
assert_equal '46ca40b9b92ee0ea1284785a5d2a9b38', post.meta
|
24
|
-
assert_equal 'dab521de65f9250b4cca7383feef67dc', post.
|
25
|
-
assert_equal
|
26
|
-
assert_equal
|
27
|
-
assert_equal
|
28
|
-
assert_equal 'test123', post.tags
|
24
|
+
assert_equal 'dab521de65f9250b4cca7383feef67dc', post.digest
|
25
|
+
assert_equal DateTime.new(2014, 6, 29, 16, 57, 45), post.time
|
26
|
+
assert_equal true, post.shared
|
27
|
+
assert_equal false, post.toread
|
28
|
+
assert_equal ['test123', 'another'], post.tags
|
29
29
|
end
|
30
30
|
end
|
@@ -4,7 +4,7 @@ require 'test_helper'
|
|
4
4
|
|
5
5
|
class PostsTest < Minitest::Test
|
6
6
|
def test_update
|
7
|
-
client =
|
7
|
+
client = mock_client_get('/posts/update',
|
8
8
|
nil,
|
9
9
|
{ 'update_time' => '2014-06-26T19:01:33Z' })
|
10
10
|
posts = Posts.new(client)
|
@@ -14,7 +14,7 @@ class PostsTest < Minitest::Test
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_add
|
17
|
-
client =
|
17
|
+
client = mock_client_action('/posts/add',
|
18
18
|
{ url: 'http://example.org',
|
19
19
|
description: 'example.org' },
|
20
20
|
{ 'result_code' => 'done' })
|
@@ -25,7 +25,7 @@ class PostsTest < Minitest::Test
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_add_with_tags
|
28
|
-
client =
|
28
|
+
client = mock_client_action('/posts/add',
|
29
29
|
{ url: 'http://example.org',
|
30
30
|
description: 'example.org',
|
31
31
|
tags: 'thumbtack test' },
|
@@ -38,7 +38,7 @@ class PostsTest < Minitest::Test
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def test_delete
|
41
|
-
client =
|
41
|
+
client = mock_client_action('/posts/delete',
|
42
42
|
{ url: 'http://example.org' },
|
43
43
|
{ 'result_code' => 'done' })
|
44
44
|
posts = Posts.new(client)
|
@@ -48,7 +48,7 @@ class PostsTest < Minitest::Test
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def test_get
|
51
|
-
client =
|
51
|
+
client = mock_client_get('/posts/get',
|
52
52
|
{ url: 'http://example.org' },
|
53
53
|
{
|
54
54
|
'date' => '2014-06-29T16:57:45Z',
|
@@ -74,7 +74,7 @@ class PostsTest < Minitest::Test
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def test_recent
|
77
|
-
client =
|
77
|
+
client = mock_client_get('/posts/recent',
|
78
78
|
{tag: 'webdev'},
|
79
79
|
{
|
80
80
|
'date' => '2014-06-29T16:57:45Z',
|
@@ -100,7 +100,7 @@ class PostsTest < Minitest::Test
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def test_all
|
103
|
-
client =
|
103
|
+
client = mock_client_get('/posts/all',
|
104
104
|
{tag: 'webdev'},
|
105
105
|
[{
|
106
106
|
'href' => 'http://example.org',
|
@@ -122,7 +122,7 @@ class PostsTest < Minitest::Test
|
|
122
122
|
end
|
123
123
|
|
124
124
|
def test_suggest
|
125
|
-
client =
|
125
|
+
client = mock_client_get('/posts/suggest',
|
126
126
|
{url: 'http://blog.com'},
|
127
127
|
[
|
128
128
|
{ 'popular' => ['blog', 'blogs', 'people'] },
|
@@ -137,7 +137,7 @@ class PostsTest < Minitest::Test
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def test_dates
|
140
|
-
client =
|
140
|
+
client = mock_client_get('/posts/dates',
|
141
141
|
{tag: 'argentina'},
|
142
142
|
{
|
143
143
|
'user' => 'user',
|
data/test/thumbtack/tags_test.rb
CHANGED
@@ -4,7 +4,7 @@ require 'test_helper'
|
|
4
4
|
|
5
5
|
class TagsTest < Minitest::Test
|
6
6
|
def test_update
|
7
|
-
client =
|
7
|
+
client = mock_client_get('/tags/get',
|
8
8
|
nil,
|
9
9
|
{
|
10
10
|
'activedesktop' => '1',
|
@@ -29,7 +29,7 @@ class TagsTest < Minitest::Test
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_delete
|
32
|
-
client =
|
32
|
+
client = mock_client_action('/tags/delete',
|
33
33
|
{ tag: 'argentina' },
|
34
34
|
{ 'result_code' => 'done' })
|
35
35
|
tags = Tags.new(client)
|
@@ -39,7 +39,7 @@ class TagsTest < Minitest::Test
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def test_rename
|
42
|
-
client =
|
42
|
+
client = mock_client_action('/tags/rename',
|
43
43
|
{ old: 'argentina', new: 'evita' },
|
44
44
|
{ 'result_code' => 'done' })
|
45
45
|
tags = Tags.new(client)
|
@@ -7,11 +7,11 @@ class BooleanTest < Minitest::Test
|
|
7
7
|
assert_equal Types::Boolean, Types::Boolean.validate(true)
|
8
8
|
assert_equal Types::Boolean, Types::Boolean.validate(false)
|
9
9
|
|
10
|
-
assert_raises(
|
10
|
+
assert_raises(ValidationError) do
|
11
11
|
Types::Boolean.validate('nope')
|
12
12
|
end
|
13
13
|
|
14
|
-
assert_raises(
|
14
|
+
assert_raises(ValidationError) do
|
15
15
|
Types::Boolean.validate(nil)
|
16
16
|
end
|
17
17
|
end
|
@@ -6,11 +6,11 @@ class DateTest < Minitest::Test
|
|
6
6
|
def test_validate
|
7
7
|
assert_equal Types::Date, Types::Date.validate(::Date.today)
|
8
8
|
|
9
|
-
assert_raises(
|
9
|
+
assert_raises(ValidationError) do
|
10
10
|
Types::Date.validate ::Date.new(0, 1, 1)
|
11
11
|
end
|
12
12
|
|
13
|
-
assert_raises(
|
13
|
+
assert_raises(ValidationError) do
|
14
14
|
Types::Date.validate ::Date.new(2101, 1, 1)
|
15
15
|
end
|
16
16
|
end
|
@@ -6,11 +6,11 @@ class DateTimeTest < Minitest::Test
|
|
6
6
|
def test_validate
|
7
7
|
assert_equal Types::DateTime, Types::DateTime.validate(::DateTime.now)
|
8
8
|
|
9
|
-
assert_raises(
|
9
|
+
assert_raises(ValidationError) do
|
10
10
|
Types::DateTime.validate ::DateTime.new(0, 1, 1)
|
11
11
|
end
|
12
12
|
|
13
|
-
assert_raises(
|
13
|
+
assert_raises(ValidationError) do
|
14
14
|
Types::DateTime.validate ::DateTime.new(2101, 1, 1)
|
15
15
|
end
|
16
16
|
end
|
@@ -24,4 +24,9 @@ class DateTimeTest < Minitest::Test
|
|
24
24
|
assert_equal ::DateTime.new(2010, 12, 11, 19, 48, 2),
|
25
25
|
Types::DateTime.from_parameter('2010-12-11T19:48:02Z')
|
26
26
|
end
|
27
|
+
|
28
|
+
def test_from_note_parameter
|
29
|
+
assert_equal ::DateTime.new(2010, 12, 11, 19, 48, 2),
|
30
|
+
Types::DateTime.from_note_parameter('2010-12-11 19:48:02')
|
31
|
+
end
|
27
32
|
end
|
@@ -7,11 +7,11 @@ class IntegerTest < Minitest::Test
|
|
7
7
|
assert_equal Types::Integer, Types::Integer.validate(0)
|
8
8
|
assert_equal Types::Integer, Types::Integer.validate(2**32)
|
9
9
|
|
10
|
-
assert_raises(
|
10
|
+
assert_raises(ValidationError) do
|
11
11
|
Types::Integer.validate(-1)
|
12
12
|
end
|
13
13
|
|
14
|
-
assert_raises(
|
14
|
+
assert_raises(ValidationError) do
|
15
15
|
Types::Integer.validate(2**32 + 1)
|
16
16
|
end
|
17
17
|
end
|
@@ -7,11 +7,11 @@ class MD5Test < Minitest::Test
|
|
7
7
|
assert_equal Types::MD5,
|
8
8
|
Types::MD5.validate('437b930db84b8079c2dd804a71936b5f')
|
9
9
|
|
10
|
-
assert_raises(
|
10
|
+
assert_raises(ValidationError) do
|
11
11
|
Types::MD5.validate('0' * 33)
|
12
12
|
end
|
13
13
|
|
14
|
-
assert_raises(
|
14
|
+
assert_raises(ValidationError) do
|
15
15
|
Types::MD5.validate('gggggggggggggggggggggggggggggggg')
|
16
16
|
end
|
17
17
|
end
|
@@ -8,11 +8,11 @@ class TagsTest < Minitest::Test
|
|
8
8
|
assert_equal Types::Tags, Types::Tags.validate(['argentina'])
|
9
9
|
assert_equal Types::Tags, Types::Tags.validate(['.private'])
|
10
10
|
|
11
|
-
assert_raises(
|
11
|
+
assert_raises(ValidationError) do
|
12
12
|
Types::Tags.validate [('x' * 256)]
|
13
13
|
end
|
14
14
|
|
15
|
-
assert_raises(
|
15
|
+
assert_raises(ValidationError) do
|
16
16
|
Types::Tags.validate ['comma,']
|
17
17
|
end
|
18
18
|
end
|
@@ -7,7 +7,7 @@ class TextTest < Minitest::Test
|
|
7
7
|
assert_equal Types::Text, Types::Text.validate('')
|
8
8
|
assert_equal Types::Text, Types::Text.validate('x' * 65536)
|
9
9
|
|
10
|
-
assert_raises(
|
10
|
+
assert_raises(ValidationError) do
|
11
11
|
Types::Text.validate('x' * 65537)
|
12
12
|
end
|
13
13
|
end
|
@@ -7,7 +7,7 @@ class TitleTest < Minitest::Test
|
|
7
7
|
assert_equal Types::Title, Types::Title.validate('')
|
8
8
|
assert_equal Types::Title, Types::Title.validate('x' * 255)
|
9
9
|
|
10
|
-
assert_raises(
|
10
|
+
assert_raises(ValidationError) do
|
11
11
|
Types::Title.validate('x' * 256)
|
12
12
|
end
|
13
13
|
end
|
data/test/thumbtack/user_test.rb
CHANGED
@@ -4,7 +4,7 @@ require 'test_helper'
|
|
4
4
|
|
5
5
|
class UserTest < Minitest::Test
|
6
6
|
def test_secret
|
7
|
-
client =
|
7
|
+
client = mock_client_get('/user/secret',
|
8
8
|
nil,
|
9
9
|
{ 'result' => '6493a84f72d86e7de130' })
|
10
10
|
user = User.new(client)
|
@@ -13,7 +13,7 @@ class UserTest < Minitest::Test
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_api_token
|
16
|
-
client =
|
16
|
+
client = mock_client_get('/user/api_token',
|
17
17
|
nil,
|
18
18
|
{ 'result' => 'XOG86E7JIYMI' })
|
19
19
|
user = User.new(client)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thumbtack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -75,6 +75,7 @@ extra_rdoc_files: []
|
|
75
75
|
files:
|
76
76
|
- lib/thumbtack.rb
|
77
77
|
- lib/thumbtack/client.rb
|
78
|
+
- lib/thumbtack/hash_to_digest.rb
|
78
79
|
- lib/thumbtack/note.rb
|
79
80
|
- lib/thumbtack/note_summary.rb
|
80
81
|
- lib/thumbtack/notes.rb
|
@@ -89,7 +90,9 @@ files:
|
|
89
90
|
- lib/thumbtack/types/date_time.rb
|
90
91
|
- lib/thumbtack/types/identity.rb
|
91
92
|
- lib/thumbtack/types/integer.rb
|
93
|
+
- lib/thumbtack/types/length_validation.rb
|
92
94
|
- lib/thumbtack/types/md5.rb
|
95
|
+
- lib/thumbtack/types/range_validation.rb
|
93
96
|
- lib/thumbtack/types/tags.rb
|
94
97
|
- lib/thumbtack/types/text.rb
|
95
98
|
- lib/thumbtack/types/title.rb
|