didkit 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7744ae1dc9ba4ca678d641139ffa94931130ee7f91fa93d3ee89e3d6e196ba8d
4
- data.tar.gz: 4111e824027b646aa8b3a21fb3032dfdba26019b6fecaf3f40f2120dfa417e52
3
+ metadata.gz: e1dfc5eee5f96a374d8f67151f8d44f5eafffc8a8e3624d7fe507e783d5ff814
4
+ data.tar.gz: 74f0cd8da39a5e723b806e07560bd15325d2176c93ca746d619975e6b0510c77
5
5
  SHA512:
6
- metadata.gz: ed96e325c2d0e8152b1db1f6a46e34afdbec049e0442f2298a89a2bbd07afb96e3ca166fca40adf1ff90d0da02997b825addd20163eb858c6074771d9ea229ce
7
- data.tar.gz: 9237183a427ad4f5380504334abcfb16a965b0e95ce39693e9aa8d58d24c6acc6c00d7150bd86be18332e02da3cbd379eb516ccb60716a3b09bf11efc155c26c
6
+ metadata.gz: 976f7d9a63ad453dde3064872dafb558cbc80f96db0a61d5243abb68664b360a38ede6cfe5a702df7609b60c6d7c6e917ffc1a0479351bb89c7c693af5ffb3c7
7
+ data.tar.gz: da1e08939b8c19d62ceef10bd48d278b7190fffdbcad65385f9ca36edd9377d0ee37d1b1d78e8ae963304dd06863cdcf9fa992a504b3e219c1f01dbf60248f6a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.2.1] - 2024-03-26
2
+
3
+ - tweaked validations in `Document` and `PLCOperation` to make them more aligned with what might be expected
4
+ - added Ruby stdlib dependencies explicitly to the gemspec
5
+
6
+ ## [0.2.0] - 2024-03-19
7
+
8
+ - added `PLCImporter` class, which lets you import operations from PLC in pages of 1000 through the "export" API
9
+ - implemented parsing of all services from DID doc & operations, not only `atproto_pds` (specifically labeller endpoints)
10
+ - allow setting the nameserver in `Resolver` initializer
11
+
1
12
  ## [0.1.0] - 2024-03-12
2
13
 
3
14
  - rejecting handles from disallowed domains like `.arpa` or `.test`
@@ -0,0 +1,13 @@
1
+ module DIDKit
2
+ module AtHandles
3
+ class FormatError < StandardError
4
+ end
5
+
6
+ def parse_also_known_as(aka)
7
+ raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.is_a?(Array)
8
+ raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.all? { |x| x.is_a?(String) }
9
+
10
+ aka.select { |x| x =~ %r(\Aat://[^/]+\z) }.map { |x| x.gsub('at://', '') }
11
+ end
12
+ end
13
+ end
@@ -1,11 +1,17 @@
1
+ require_relative 'at_handles'
1
2
  require_relative 'resolver'
3
+ require_relative 'service_record'
4
+ require_relative 'services'
2
5
 
3
6
  module DIDKit
4
7
  class Document
5
8
  class FormatError < StandardError
6
9
  end
7
10
 
8
- attr_reader :json, :did, :pds_endpoint, :handles
11
+ include AtHandles
12
+ include Services
13
+
14
+ attr_reader :json, :did, :handles, :services
9
15
 
10
16
  def initialize(did, json)
11
17
  raise FormatError, "Missing id field" if json['id'].nil?
@@ -15,29 +21,20 @@ module DIDKit
15
21
  @did = did
16
22
  @json = json
17
23
 
18
- service = json['service']
19
- raise FormatError, "Missing service key" if service.nil?
20
- raise FormatError, "Invalid service data" unless service.is_a?(Array) && service.all? { |x| x.is_a?(Hash) }
21
-
22
- if pds = service.detect { |x| x['id'] == '#atproto_pds' }
23
- raise FormatError, "Missing PDS type" unless pds['type']
24
- raise FormatError, "Invalid PDS type" unless pds['type'] == 'AtprotoPersonalDataServer'
25
- raise FormatError, "Missing PDS endpoint" unless pds['serviceEndpoint']
26
- raise FormatError, "Invalid PDS endpoint" unless pds['serviceEndpoint'].is_a?(String)
27
- raise FormatError, "Invalid PDS endpoint" unless pds['serviceEndpoint'] =~ %r(://)
24
+ if service = json['service']
25
+ raise FormatError, "Invalid service data" unless service.is_a?(Array) && service.all? { |x| x.is_a?(Hash) }
28
26
 
29
- @pds_endpoint = pds['serviceEndpoint']
30
- end
27
+ @services = service.filter_map { |x|
28
+ id, type, endpoint = x.values_at('id', 'type', 'serviceEndpoint')
29
+ next unless id.is_a?(String) && id.start_with?('#') && type.is_a?(String) && endpoint.is_a?(String)
31
30
 
32
- if aka = json['alsoKnownAs']
33
- raise FormatError, "Invalid alsoKnownAs" unless aka.is_a?(Array)
34
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x.is_a?(String) }
35
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x =~ %r(\Aat://[^/]+\z) }
36
-
37
- @handles = aka.map { |x| x.gsub('at://', '') }
31
+ ServiceRecord.new(id.gsub(/^#/, ''), type, endpoint)
32
+ }
38
33
  else
39
- @handles = []
34
+ @services = []
40
35
  end
36
+
37
+ @handles = parse_also_known_as(json['alsoKnownAs'] || [])
41
38
  end
42
39
 
43
40
  def get_validated_handle
@@ -1,5 +1,4 @@
1
1
  require 'json'
2
- require 'net/http'
3
2
  require 'open-uri'
4
3
  require 'time'
5
4
 
@@ -8,21 +7,42 @@ require_relative 'plc_operation'
8
7
  module DIDKit
9
8
  class PLCImporter
10
9
  PLC_SERVICE = 'plc.directory'
10
+ MAX_PAGE = 1000
11
11
 
12
- attr_accessor :ignore_errors
12
+ attr_accessor :ignore_errors, :last_date
13
+
14
+ def initialize(since: nil)
15
+ if since.to_s == 'beginning'
16
+ @last_date = nil
17
+ elsif since.is_a?(String)
18
+ @last_date = Time.parse(since)
19
+ elsif since
20
+ @last_date = since
21
+ else
22
+ @last_date = Time.now
23
+ @eof = true
24
+ end
13
25
 
14
- def initialize(last_date = Time.now)
15
- @last_date = last_date
16
26
  @ignore_errors = false
17
27
  end
18
28
 
19
- def fetch
20
- url = URI("https://#{PLC_SERVICE}/export")
21
- url.query = URI.encode_www_form(:after => @last_date.utc.iso8601(6)) if @last_date
22
- request_time = Time.now
29
+ def plc_service
30
+ PLC_SERVICE
31
+ end
32
+
33
+ def get_export(args = {})
34
+ url = URI("https://#{plc_service}/export")
35
+ url.query = URI.encode_www_form(args)
23
36
 
24
37
  data = URI.open(url).read
25
- rows = data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) }
38
+ data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) }
39
+ end
40
+
41
+ def fetch_page
42
+ request_time = Time.now
43
+
44
+ query = @last_date ? { :after => @last_date.utc.iso8601(6) } : {}
45
+ rows = get_export(query)
26
46
 
27
47
  operations = rows.filter_map do |json|
28
48
  begin
@@ -32,14 +52,22 @@ module DIDKit
32
52
  end
33
53
  end
34
54
 
35
- block.call(operations)
55
+ @last_date = operations.last&.created_at || request_time
56
+ @eof = (rows.length < MAX_PAGE)
36
57
 
37
- if rows.length == 1000
38
- @last_date = operations.last.created_at || request_time
39
- run_update
40
- else
41
- @last_date = request_time
58
+ operations
59
+ end
60
+
61
+ def fetch(&block)
62
+ loop do
63
+ operations = fetch_page
64
+ block.call(operations)
65
+ break if eof?
42
66
  end
43
67
  end
68
+
69
+ def eof?
70
+ !!@eof
71
+ end
44
72
  end
45
73
  end
@@ -1,56 +1,57 @@
1
1
  require 'time'
2
2
 
3
+ require_relative 'at_handles'
4
+ require_relative 'service_record'
5
+ require_relative 'services'
6
+
3
7
  module DIDKit
4
8
  class PLCOperation
5
9
  class FormatError < StandardError
6
10
  end
7
11
 
8
- attr_reader :did, :created_at, :type, :pds_endpoint, :handles
12
+ include AtHandles
13
+ include Services
14
+
15
+ attr_reader :json, :did, :created_at, :type, :handles, :services
9
16
 
10
17
  def initialize(json)
18
+ @json = json
11
19
  @did = json['did']
12
- raise FormatError, "Missing DID" if @did.nil?
13
- raise FormatError, "Invalid DID" unless @did.is_a?(String) && @did.start_with?('did:')
20
+ raise FormatError, "Missing DID: #{json}" if @did.nil?
21
+ raise FormatError, "Invalid DID: #{@did}" unless @did.is_a?(String) && @did.start_with?('did:')
14
22
 
15
23
  timestamp = json['createdAt']
16
- raise FormatError, "Missing createdAt" if timestamp.nil?
17
- raise FormatError, "Invalid createdAt" unless timestamp.is_a?(String)
24
+ raise FormatError, "Missing createdAt: #{json}" if timestamp.nil?
25
+ raise FormatError, "Invalid createdAt: #{timestamp.inspect}" unless timestamp.is_a?(String)
18
26
 
19
27
  @created_at = Time.parse(timestamp)
20
28
 
21
29
  operation = json['operation']
22
- raise FormatError, "Missing operation key" if operation.nil?
23
- raise FormatError, "Invalid operation data" unless operation.is_a?(Hash)
30
+ raise FormatError, "Missing operation key: #{json}" if operation.nil?
31
+ raise FormatError, "Invalid operation data: #{operation.inspect}" unless operation.is_a?(Hash)
24
32
 
25
33
  type = operation['type']
26
- raise FormatError, "Missing type" if type.nil?
34
+ raise FormatError, "Missing operation type: #{json}" if type.nil?
27
35
 
28
36
  @type = type.to_sym
29
37
  return unless @type == :plc_operation
30
38
 
31
39
  services = operation['services']
32
- raise FormatError, "Missing services key" if services.nil?
33
- raise FormatError, "Invalid services data" unless services.is_a?(Hash)
34
-
35
- if pds = services['atproto_pds']
36
- raise FormatError, "Invalid PDS data" unless pds.is_a?(Hash)
37
- raise FormatError, "Missing PDS type" unless pds['type']
38
- raise FormatError, "Invalid PDS type" unless pds['type'] == 'AtprotoPersonalDataServer'
39
- raise FormatError, "Missing PDS endpoint" unless pds['endpoint']
40
- raise FormatError, "Invalid PDS endpoint" unless pds['endpoint'].is_a?(String) && pds['endpoint'] =~ %r(://)
41
-
42
- @pds_endpoint = pds['endpoint']
43
- end
44
-
45
- if aka = operation['alsoKnownAs']
46
- raise FormatError, "Invalid alsoKnownAs" unless aka.is_a?(Array)
47
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x.is_a?(String) }
48
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x =~ %r(\Aat://[^/]+\z) }
49
-
50
- @handles = aka.map { |x| x.gsub('at://', '') }
51
- else
52
- @handles = []
53
- end
40
+ raise FormatError, "Missing services key: #{json}" if services.nil?
41
+ raise FormatError, "Invalid services data: #{services}" unless services.is_a?(Hash)
42
+
43
+ @services = services.map { |k, x|
44
+ type, endpoint = x.values_at('type', 'endpoint')
45
+
46
+ raise FormatError, "Missing service type" unless type
47
+ raise FormatError, "Invalid service type: #{type.inspect}" unless type.is_a?(String)
48
+ raise FormatError, "Missing service endpoint" unless endpoint
49
+ raise FormatError, "Invalid service endpoint: #{endpoint.inspect}" unless endpoint.is_a?(String)
50
+
51
+ ServiceRecord.new(k, type, endpoint)
52
+ }
53
+
54
+ @handles = parse_also_known_as(operation['alsoKnownAs'])
54
55
  end
55
56
  end
56
57
  end
@@ -13,6 +13,10 @@ module DIDKit
13
13
 
14
14
  attr_accessor :nameserver
15
15
 
16
+ def initialize(options = {})
17
+ @nameserver = options[:nameserver]
18
+ end
19
+
16
20
  def resolve_handle(handle)
17
21
  domain = handle.gsub(/^@/, '')
18
22
 
@@ -0,0 +1,20 @@
1
+ require 'uri'
2
+ require_relative 'errors'
3
+
4
+ module DIDKit
5
+ class ServiceRecord
6
+ attr_reader :key, :type, :endpoint
7
+
8
+ def initialize(key, type, endpoint)
9
+ begin
10
+ uri = URI(endpoint)
11
+ rescue URI::Error
12
+ raise FormatError, "Invalid service endpoint: #{endpoint.inspect}"
13
+ end
14
+
15
+ @key = key
16
+ @type = type
17
+ @endpoint = endpoint
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module DIDKit
2
+ module Services
3
+ def get_service(key, type)
4
+ @services&.detect { |s| s.key == key && s.type == type }
5
+ end
6
+
7
+ def pds_endpoint
8
+ @pds_endpoint ||= get_service('atproto_pds', 'AtprotoPersonalDataServer')&.endpoint
9
+ end
10
+
11
+ def labeler_endpoint
12
+ @labeler_endpoint ||= get_service('atproto_labeler', 'AtprotoLabeler')&.endpoint
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DIDKit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/didkit.rb CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative "didkit/did"
4
4
  require_relative "didkit/document"
5
+ require_relative "didkit/plc_importer"
6
+ require_relative "didkit/plc_operation"
7
+ require_relative "didkit/resolver"
5
8
  require_relative "didkit/version"
6
9
 
7
10
  module DIDKit
metadata CHANGED
@@ -1,15 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: didkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-12 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-03-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-http
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: open-uri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: resolv
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: time
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.3'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: uri
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.13'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.13'
13
97
  description:
14
98
  email:
15
99
  - jakub.suder@gmail.com
@@ -21,12 +105,15 @@ files:
21
105
  - LICENSE.txt
22
106
  - README.md
23
107
  - lib/didkit.rb
108
+ - lib/didkit/at_handles.rb
24
109
  - lib/didkit/did.rb
25
110
  - lib/didkit/document.rb
26
111
  - lib/didkit/errors.rb
27
112
  - lib/didkit/plc_importer.rb
28
113
  - lib/didkit/plc_operation.rb
29
114
  - lib/didkit/resolver.rb
115
+ - lib/didkit/service_record.rb
116
+ - lib/didkit/services.rb
30
117
  - lib/didkit/version.rb
31
118
  - sig/didkit.rbs
32
119
  homepage: https://github.com/mackuba/didkit