didkit 0.3.1 → 0.3.2

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: b81757b37a0aa45a3ccefe6e8ebac777a283a6f8d3b827308f1f029b30abe0c8
4
- data.tar.gz: beef9f4a9cc71c9edc46fce5e95df2b1c6a442c223826054eafed721d744b8ad
3
+ metadata.gz: d7a4ebcd65dd0bb01a1dfd790dafaa6db2780e704558b8da171eae1db45053d5
4
+ data.tar.gz: 27bd5afc5f6bec8207f07b152092337c028abfee26cf0684f5c04d1352241b03
5
5
  SHA512:
6
- metadata.gz: 24c046d3566c3816c700e60936f9313b837df8403dc0435e67c317eabaf4e554c29357573a825d72a4795185d942c67c0fc408457a2f3c72e8569c1fc9c718ab
7
- data.tar.gz: d0e4bb8224a6a0c5b6d62edfee27869fc7227229d3d95f482beb2c917ac0ea99c45fed7e8993299c8a2b69ef0a9d5f58e1d610e108f0f15aac50ffebb7599dfe
6
+ metadata.gz: 5cfcbe474eb92088fef122b10df8776b99589a5aa8f1725c5303305cc5ece4696d291f82931e691c92d7b3eb16b0f874efc0fb8bfbfc01f4267defa4e3cc28e4
7
+ data.tar.gz: 984039e433ef24d7499c70c5bcd336c4f826195b59b3143a03ca131741146c18fdae3b9088a201b3556169871b8ddd93062e7dfcc7292db4294fb45e464b9e17
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.3.2] - 2026-02-15
2
+
3
+ - added YARD API documentation
4
+ - marked some helper methods in `requests.rb`, `at_handles.rb` and `DIDKit::Resolver` as private
5
+ - merged all "FormatErrors" into one `DIDKit::FormatError`
6
+ - added `frozen_string_literal` directive everywhere to minimize garbage collection
7
+
1
8
  ## [0.3.1] - 2025-12-19
2
9
 
3
10
  - allow passing a DID string or object to `#resolve_handle` and just return that DID – so you can have a script that accepts either a handle or a DID, and passes the input to `DID.resolve_handle` without checking which one it is
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The zlib License
2
2
 
3
- Copyright (c) 2025 Jakub Suder
3
+ Copyright (c) 2026 Jakub Suder
4
4
 
5
5
  This software is provided 'as-is', without any express or implied
6
6
  warranty. In no event will the authors be held liable for any damages
data/README.md CHANGED
@@ -13,11 +13,13 @@ Accounts on Bluesky use identifiers like [did:plc:oio4hkxaop4ao4wz2pp3f4cr](http
13
13
 
14
14
  ## Installation
15
15
 
16
- From the command line:
16
+ To use DIDKit, you need a reasonably new version of Ruby – it should run on Ruby 2.6 and above, although it's recommended to use a version that's still getting maintainance updates, i.e. currently 3.2+. A compatible version should be preinstalled on macOS Big Sur and above and on many Linux systems. Otherwise, you can install one using tools such as [RVM](https://rvm.io), [asdf](https://asdf-vm.com), [ruby-install](https://github.com/postmodern/ruby-install) or [ruby-build](https://github.com/rbenv/ruby-build), or `rpm` or `apt-get` on Linux (see more installation options on [ruby-lang.org](https://www.ruby-lang.org/en/downloads/)).
17
17
 
18
- gem install didkit
18
+ To install the gem, run in the command line:
19
19
 
20
- Or, add this to your `Gemfile`:
20
+ [sudo] gem install didkit
21
+
22
+ Or add this to your app's `Gemfile`:
21
23
 
22
24
  gem 'didkit', '~> 0.3'
23
25
 
@@ -106,6 +108,8 @@ resolver.get_verified_handle(did)
106
108
 
107
109
  ## Credits
108
110
 
109
- Copyright © 2025 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr)).
111
+ Copyright © 2026 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr)).
110
112
 
111
113
  The code is available under the terms of the [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
114
+
115
+ Bug reports and pull requests are welcome 😎
@@ -1,7 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors'
4
+
1
5
  module DIDKit
6
+
7
+ #
8
+ # @private
9
+ #
10
+
2
11
  module AtHandles
3
- class FormatError < StandardError
4
- end
12
+
13
+ private
5
14
 
6
15
  def parse_also_known_as(aka)
7
16
  raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.is_a?(Array)
data/lib/didkit/did.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'uri'
3
5
 
@@ -6,16 +8,52 @@ require_relative 'requests'
6
8
  require_relative 'resolver'
7
9
 
8
10
  module DIDKit
11
+
12
+ #
13
+ # Represents a DID identifier (account on the ATProto network). This class serves as an entry
14
+ # point to various lookup helpers. For convenience it can also be accessed as just `DID` without
15
+ # the `DIDKit::` prefix.
16
+ #
17
+ # @example Resolving a handle
18
+ # did = DID.resolve_handle('bsky.app')
19
+ #
20
+
9
21
  class DID
10
22
  GENERIC_REGEXP = /\Adid\:\w+\:.+\z/
11
23
 
12
24
  include Requests
13
25
 
26
+ # Resolve a handle into a DID. Looks up the given ATProto domain handle using the DNS TXT method
27
+ # and the HTTP .well-known method and returns a DID if one is assigned using either of the methods.
28
+ #
29
+ # If a DID string or a {DID} object is passed, it simply returns that DID, so you can use this
30
+ # method to pass it an input string from the user which can be a DID or handle, without having to
31
+ # check which one it is.
32
+ #
33
+ # @param handle [String, DID] a domain handle (may start with an `@`) or a DID string
34
+ # @return [DID, nil] resolved DID if found, nil otherwise
35
+
14
36
  def self.resolve_handle(handle)
15
37
  Resolver.new.resolve_handle(handle)
16
38
  end
17
39
 
18
- attr_reader :type, :did, :resolved_by
40
+ # @return [Symbol] DID type (`:plc` or `:web`)
41
+ attr_reader :type
42
+
43
+ # @return [String] DID identifier string
44
+ attr_reader :did
45
+
46
+ # @return [Symbol, nil] `:dns` or `:http` if the DID was looked up using one of those methods
47
+ attr_reader :resolved_by
48
+
49
+ alias to_s did
50
+
51
+
52
+ # Create a DID object from a DID string.
53
+ #
54
+ # @param did [String, DID] DID string or another DID object
55
+ # @param resolved_by [Symbol, nil] optionally, how the DID was looked up (`:dns` or `:http`)
56
+ # @raise [DIDError] when the DID format or type is invalid
19
57
 
20
58
  def initialize(did, resolved_by = nil)
21
59
  if did.is_a?(DID)
@@ -36,20 +74,39 @@ module DIDKit
36
74
  @resolved_by = resolved_by
37
75
  end
38
76
 
39
- alias to_s did
77
+ # Returns or looks up the DID document with the DID's identity details from an appropriate source.
78
+ # This method caches the document in a local variable if it's called again.
79
+ #
80
+ # @return [Document] resolved DID document
40
81
 
41
82
  def document
42
83
  @document ||= get_document
43
84
  end
44
85
 
86
+ # Looks up the DID document with the DID's identity details from an appropriate source.
87
+ # @return [Document] resolved DID document
88
+
45
89
  def get_document
46
90
  Resolver.new.resolve_did(self)
47
91
  end
48
92
 
93
+ # Returns the first verified handle assigned to this DID.
94
+ #
95
+ # Looks up the domain handles assigned to this DID in its DID document, checks if they are
96
+ # verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns
97
+ # the first handle that validates correctly, or nil if none matches.
98
+ #
99
+ # @return [String, nil] verified handle domain, if found
100
+
49
101
  def get_verified_handle
50
102
  Resolver.new.get_verified_handle(document)
51
103
  end
52
104
 
105
+ # Fetches the PLC audit log (list of all previous operations) for a did:plc DID.
106
+ #
107
+ # @return [Array<PLCOperation>] list of PLC operations in the audit log
108
+ # @raise [DIDError] when the DID is not a did:plc
109
+
53
110
  def get_audit_log
54
111
  if @type == :plc
55
112
  PLCImporter.new.fetch_audit_log(self)
@@ -58,10 +115,23 @@ module DIDKit
58
115
  end
59
116
  end
60
117
 
118
+ # Returns the domain portion of a did:web identifier.
119
+ #
120
+ # @return [String, nil] DID domain if the DID is a did:web, nil for did:plc
121
+
61
122
  def web_domain
62
123
  did.gsub(/^did\:web\:/, '') if type == :web
63
124
  end
64
125
 
126
+ # Checks the status of the account/repo on its own PDS using the `getRepoStatus` endpoint.
127
+ #
128
+ # @param request_options [Hash] request options to override
129
+ # @option request_options [Integer] :timeout request timeout (default: 15)
130
+ # @option request_options [Integer] :max_redirects maximum number of redirects to follow (default: 5)
131
+ #
132
+ # @return [Symbol, nil] `:active`, or returned inactive status, or `nil` if account is not found
133
+ # @raise [APIError] when the response is invalid
134
+
65
135
  def account_status(request_options = {})
66
136
  doc = self.document
67
137
  return nil if doc.pds_endpoint.nil?
@@ -91,14 +161,31 @@ module DIDKit
91
161
  end
92
162
  end
93
163
 
164
+ # Checks if the account is seen as active on its own PDS, using the `getRepoStatus` endpoint.
165
+ # This is a helper which calls the {#account_status} method and checks if the status is `:active`.
166
+ #
167
+ # @return [Boolean] true if the returned status is active
168
+ # @raise [APIError] when the response is invalid
169
+
94
170
  def account_active?
95
171
  account_status == :active
96
172
  end
97
173
 
174
+ # Checks if the account exists its own PDS, using the `getRepoStatus` endpoint.
175
+ # This is a helper which calls the {#account_status} method and checks if the repo is found at all.
176
+ #
177
+ # @return [Boolean] true if the returned status is valid, false if repo is not found
178
+ # @raise [APIError] when the response is invalid
179
+
98
180
  def account_exists?
99
181
  account_status != nil
100
182
  end
101
183
 
184
+ # Compares the DID to another DID object or string.
185
+ #
186
+ # @param other [DID, String] other DID to compare with
187
+ # @return [Boolean] true if it's the same DID
188
+
102
189
  def ==(other)
103
190
  if other.is_a?(String)
104
191
  self.did == other
@@ -1,17 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'at_handles'
4
+ require_relative 'errors'
2
5
  require_relative 'resolver'
3
6
  require_relative 'service_record'
4
7
  require_relative 'services'
5
8
 
6
9
  module DIDKit
7
- class Document
8
- class FormatError < StandardError
9
- end
10
10
 
11
+ #
12
+ # Parsed DID document from a JSON file loaded from [plc.directory](https://plc.directory) or a did:web domain.
13
+ #
14
+ # Use {DID#document} or {Resolver#resolve_did} to fetch a DID document and return this object.
15
+ #
16
+
17
+ class Document
11
18
  include AtHandles
12
19
  include Services
13
20
 
14
- attr_reader :json, :did, :handles, :services
21
+ # @return [Hash] the complete JSON data of the DID document
22
+ attr_reader :json
23
+
24
+ # @return [DID] the DID that this document describes
25
+ attr_reader :did
26
+
27
+ # Returns a list of handles assigned to this DID in its DID document.
28
+ #
29
+ # Note: the handles aren't guaranteed to be verified (validated in the other direction).
30
+ # Use {#get_verified_handle} to find a handle that is correctly verified.
31
+ #
32
+ # @return [Array<String>]
33
+ attr_reader :handles
34
+
35
+ # @return [Array<ServiceRecords>] service records like PDS details assigned to the DID
36
+ attr_reader :services
37
+
38
+ # Creates a DID document object.
39
+ #
40
+ # @param did [DID] DID object
41
+ # @param json [Hash] DID document JSON
42
+ # @raise [FormatError] when required fields are missing or invalid.
15
43
 
16
44
  def initialize(did, json)
17
45
  raise FormatError, "Missing id field" if json['id'].nil?
@@ -25,10 +53,19 @@ module DIDKit
25
53
  @handles = parse_also_known_as(json['alsoKnownAs'] || [])
26
54
  end
27
55
 
56
+ # Returns the first verified handle assigned to the DID.
57
+ #
58
+ # Looks up the domain handles assigned to this DID in the DID document, checks if they are
59
+ # verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns
60
+ # the first handle that validates correctly, or nil if none matches.
61
+ #
62
+ # @return [String, nil] verified handle domain, if found
63
+
28
64
  def get_verified_handle
29
65
  Resolver.new.get_verified_handle(self)
30
66
  end
31
67
 
68
+
32
69
  private
33
70
 
34
71
  def parse_services(service_data)
data/lib/didkit/errors.rb CHANGED
@@ -1,21 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DIDKit
2
- class DIDError < StandardError
3
- end
4
4
 
5
+ #
6
+ # Raised when an HTTP request returns a response with an error status.
7
+ #
5
8
  class APIError < StandardError
9
+
10
+ # @return [Net::HTTPResponse] the returned HTTP response
6
11
  attr_reader :response
7
12
 
13
+ # @param response [Net::HTTPResponse] the returned HTTP response
8
14
  def initialize(response)
9
15
  @response = response
10
16
  super("APIError: #{response}")
11
17
  end
12
18
 
19
+ # @return [Integer] HTTP status code
13
20
  def status
14
21
  response.code.to_i
15
22
  end
16
23
 
24
+ # @return [String] HTTP response body
17
25
  def body
18
26
  response.body
19
27
  end
20
28
  end
29
+
30
+ #
31
+ # Raised when a string is not a valid DID or not of the right type.
32
+ #
33
+ class DIDError < StandardError
34
+ end
35
+
36
+ #
37
+ # Raised when the loaded data has some missing or invalid fields.
38
+ #
39
+ class FormatError < StandardError
40
+ end
21
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'time'
3
5
  require 'uri'
@@ -1,18 +1,60 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
 
3
5
  require_relative 'at_handles'
6
+ require_relative 'errors'
4
7
  require_relative 'service_record'
5
8
  require_relative 'services'
6
9
 
7
10
  module DIDKit
8
- class PLCOperation
9
- class FormatError < StandardError
10
- end
11
11
 
12
+ #
13
+ # Represents a single operation of changing a specific DID's data in the [plc.directory](https://plc.directory)
14
+ # (e.g. changing assigned handles or migrating to a different PDS).
15
+ #
16
+
17
+ class PLCOperation
12
18
  include AtHandles
13
19
  include Services
14
20
 
15
- attr_reader :json, :did, :cid, :seq, :created_at, :type, :handles, :services
21
+ # @return [Hash] the JSON from which the operation is parsed
22
+ attr_reader :json
23
+
24
+ # @return [String] the DID which the operation concerns
25
+ attr_reader :did
26
+
27
+ # @return [String] CID (Content Identifier) of the operation
28
+ attr_reader :cid
29
+
30
+ # Returns a sequential number of the operation (only used in the new export API).
31
+ # @return [Integer, nil] sequential number of the operation
32
+ attr_reader :seq
33
+
34
+ # @return [Time] time when the operation was created
35
+ attr_reader :created_at
36
+
37
+ # Returns the `type` field of the operation (usually `"plc_operation"`).
38
+ # @return [String] the operation type
39
+ attr_reader :type
40
+
41
+ # Returns a list of handles assigned to the DID in this operation.
42
+ #
43
+ # Note: the handles aren't guaranteed to be verified (validated in the other direction).
44
+ # Use {DID#get_verified_handle} or {Document#get_verified_handle} to find a handle that is
45
+ # correctly verified.
46
+ #
47
+ # @return [Array<String>]
48
+ attr_reader :handles
49
+
50
+ # @return [Array<ServiceRecords>] service records like PDS details assigned to the DID
51
+ attr_reader :services
52
+
53
+
54
+ # Creates a PLCOperation object.
55
+ #
56
+ # @param json [Hash] operation JSON
57
+ # @raise [FormatError] when required fields are missing or invalid
16
58
 
17
59
  def initialize(json)
18
60
  @json = json
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'net/http'
3
5
  require 'uri'
@@ -5,7 +7,15 @@ require 'uri'
5
7
  require_relative 'errors'
6
8
 
7
9
  module DIDKit
10
+
11
+ #
12
+ # @private
13
+ #
14
+
8
15
  module Requests
16
+
17
+ private
18
+
9
19
  def get_response(url, options = {})
10
20
  url = URI(url) unless url.is_a?(URI)
11
21
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'net/http'
2
4
  require 'resolv'
3
5
 
@@ -6,18 +8,41 @@ require_relative 'document'
6
8
  require_relative 'requests'
7
9
 
8
10
  module DIDKit
11
+
12
+ #
13
+ # A class which manages resolving of handles to DIDs and DIDs to DID documents.
14
+ #
15
+
9
16
  class Resolver
17
+ # These TLDs are not allowed in ATProto handles, so the resolver returns nil for them
18
+ # without trying to look them up.
10
19
  RESERVED_DOMAINS = %w(alt arpa example internal invalid local localhost onion test)
11
20
 
12
21
  include Requests
13
22
 
23
+ # @return [String, Array<String>] custom DNS nameserver(s) to use for DNS TXT lookups
14
24
  attr_accessor :nameserver
15
25
 
26
+ # @param options [Hash] resolver options
27
+ # @option options [String, Array<String>] :nameserver custom DNS nameserver(s) to use (IP or an array of IPs)
28
+ # @option options [Integer] :timeout request timeout in seconds (default: 15)
29
+ # @option options [Integer] :max_redirects maximum number of redirects to follow (default: 5)
30
+
16
31
  def initialize(options = {})
17
32
  @nameserver = options[:nameserver]
18
33
  @request_options = options.slice(:timeout, :max_redirects)
19
34
  end
20
35
 
36
+ # Resolve a handle into a DID. Looks up the given ATProto domain handle using the DNS TXT method
37
+ # and the HTTP .well-known method and returns a DID if one is assigned using either of the methods.
38
+ #
39
+ # If a DID string or a {DID} object is passed, it simply returns that DID, so you can use this
40
+ # method to pass it an input string from the user which can be a DID or handle, without having to
41
+ # check which one it is.
42
+ #
43
+ # @param handle [String, DID] a domain handle (may start with an `@`) or a DID string
44
+ # @return [DID, nil] resolved DID if found, nil otherwise
45
+
21
46
  def resolve_handle(handle)
22
47
  if handle.is_a?(DID) || handle =~ DID::GENERIC_REGEXP
23
48
  return DID.new(handle)
@@ -36,6 +61,14 @@ module DIDKit
36
61
  end
37
62
  end
38
63
 
64
+ # Tries to resolve a handle into DID using the DNS TXT method.
65
+ #
66
+ # Checks the DNS records for a given domain for an entry `_atproto.#{domain}` whose value is
67
+ # a correct DID string.
68
+ #
69
+ # @param domain [String] a domain handle to look up
70
+ # @return [String, nil] resolved DID if found, nil otherwise
71
+
39
72
  def resolve_handle_by_dns(domain)
40
73
  dns_records = Resolv::DNS.open(resolv_options) do |d|
41
74
  d.getresources("_atproto.#{domain}", Resolv::DNS::Resource::IN::TXT)
@@ -50,6 +83,14 @@ module DIDKit
50
83
  nil
51
84
  end
52
85
 
86
+ # Tries to resolve a handle into DID using the HTTP .well-known method.
87
+ #
88
+ # Checks the `/.well-known/atproto-did` endpoint on the given domain to see if it returns
89
+ # a text file that contains a correct DID string.
90
+ #
91
+ # @param domain [String] a domain handle to look up
92
+ # @return [String, nil] resolved DID if found, nil otherwise
93
+
53
94
  def resolve_handle_by_well_known(domain)
54
95
  url = "https://#{domain}/.well-known/atproto-did"
55
96
  response = get_response(url, @request_options)
@@ -63,6 +104,49 @@ module DIDKit
63
104
  nil
64
105
  end
65
106
 
107
+ # Resolve a DID to a DID document.
108
+ #
109
+ # Looks up the DID document with the DID's identity details from an appropriate source, i.e. either
110
+ # [plc.directory](https://plc.directory) for did:plc DIDs, or the did:web's domain for did:web DIDs.
111
+ #
112
+ # @param did [String, DID] DID string or object
113
+ # @return [Document] resolved DID document
114
+ # @raise [APIError] if an incorrect response is returned
115
+
116
+ def resolve_did(did)
117
+ did = DID.new(did) if did.is_a?(String)
118
+
119
+ did.type == :plc ? resolve_did_plc(did) : resolve_did_web(did)
120
+ end
121
+
122
+ # Returns the first verified handle assigned to the given DID.
123
+ #
124
+ # Looks up the domain handles assigned to the DID in the DID document, checks if they are
125
+ # verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns
126
+ # the first handle that validates correctly, or nil if none matches.
127
+ #
128
+ # @param subject [String, DID, Document] a DID or its DID document
129
+ # @return [String, nil] verified handle domain, if found
130
+
131
+ def get_verified_handle(subject)
132
+ document = subject.is_a?(Document) ? subject : resolve_did(subject)
133
+
134
+ first_verified_handle(document.did, document.handles)
135
+ end
136
+
137
+ # Returns the first handle from the list that resolves back to the given DID.
138
+ #
139
+ # @param did [DID, String] DID to verify the handles against
140
+ # @param handles [Array<String>] handles to check
141
+ # @return [String, nil] a verified handle, if found
142
+
143
+ def first_verified_handle(did, handles)
144
+ handles.detect { |h| resolve_handle(h) == did.to_s }
145
+ end
146
+
147
+
148
+ private
149
+
66
150
  def resolv_options
67
151
  options = Resolv::DNS::Config.default_config_hash.dup
68
152
  options[:nameserver] = nameserver if nameserver
@@ -78,12 +162,6 @@ module DIDKit
78
162
  text.lines.length == 1 && text =~ DID::GENERIC_REGEXP ? text : nil
79
163
  end
80
164
 
81
- def resolve_did(did)
82
- did = DID.new(did) if did.is_a?(String)
83
-
84
- did.type == :plc ? resolve_did_plc(did) : resolve_did_web(did)
85
- end
86
-
87
165
  def resolve_did_plc(did)
88
166
  json = get_json("https://plc.directory/#{did}", content_type: /^application\/did\+ld\+json(;.+)?$/)
89
167
  Document.new(did, json)
@@ -93,15 +171,5 @@ module DIDKit
93
171
  json = get_json("https://#{did.web_domain}/.well-known/did.json")
94
172
  Document.new(did, json)
95
173
  end
96
-
97
- def get_verified_handle(subject)
98
- document = subject.is_a?(Document) ? subject : resolve_did(subject)
99
-
100
- first_verified_handle(document.did, document.handles)
101
- end
102
-
103
- def first_verified_handle(did, handles)
104
- handles.detect { |h| resolve_handle(h) == did.to_s }
105
- end
106
174
  end
107
175
  end
@@ -1,12 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require_relative 'errors'
3
5
 
4
6
  module DIDKit
7
+
8
+ # A parsed service record from either a DID document's `service` field or a PLC directory
9
+ # operation's `services` field.
10
+
5
11
  class ServiceRecord
6
- class FormatError < StandardError
7
- end
8
12
 
9
- attr_reader :key, :type, :endpoint
13
+ # Returns the service's identifier (without `#`), like "atproto_pds".
14
+ # @return [String] service's identifier
15
+ attr_reader :key
16
+
17
+ # Returns the service's type field, like "AtprotoPersonalDataServer".
18
+ # @return [String] service's type
19
+ attr_reader :type
20
+
21
+ # @return [String] service's endpoint URL
22
+ attr_reader :endpoint
23
+
24
+ # Create a service record from DID document fields.
25
+ #
26
+ # @param key [String] service identifier (without `#`)
27
+ # @param type [String] service type
28
+ # @param endpoint [String] service endpoint URL
29
+ # @raise [FormatError] when the endpoint is not a valid URI
10
30
 
11
31
  def initialize(key, type, endpoint)
12
32
  begin
@@ -1,23 +1,65 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
 
3
5
  module DIDKit
6
+
7
+ #
8
+ # @api private
9
+ #
10
+
4
11
  module Services
12
+
13
+ # Finds a service entry matching the given key and type.
14
+ #
15
+ # @api public
16
+ # @param key [String] service key in the DID document
17
+ # @param type [String] service type identifier
18
+ # @return [ServiceRecord, nil] matching service record, if found
19
+
5
20
  def get_service(key, type)
6
21
  @services&.detect { |s| s.key == key && s.type == type }
7
22
  end
8
23
 
24
+ # Returns the PDS service endpoint, if present.
25
+ #
26
+ # If the DID has an `#atproto_pds` service declared in its `service` section,
27
+ # returns the URL in its `serviceEndpoint` field. In other words, this is the URL
28
+ # of the PDS assigned to a given user, which stores the user's account and repo.
29
+ #
30
+ # @api public
31
+ # @return [String, nil] PDS service endpoint URL
32
+
9
33
  def pds_endpoint
10
34
  @pds_endpoint ||= get_service('atproto_pds', 'AtprotoPersonalDataServer')&.endpoint
11
35
  end
12
36
 
37
+ # Returns the labeler service endpoint, if present.
38
+ #
39
+ # If the DID has an `#atproto_labeler` service declared in its `service` section,
40
+ # returns the URL in its `serviceEndpoint` field.
41
+ #
42
+ # @api public
43
+ # @return [String, nil] labeler service endpoint URL
44
+
13
45
  def labeler_endpoint
14
46
  @labeler_endpoint ||= get_service('atproto_labeler', 'AtprotoLabeler')&.endpoint
15
47
  end
16
48
 
49
+ # Returns the hostname of the PDS service, if present.
50
+ #
51
+ # @api public
52
+ # @return [String, nil] hostname of the PDS endpoint URL
53
+
17
54
  def pds_host
18
55
  pds_endpoint&.then { |x| URI(x).host }
19
56
  end
20
57
 
58
+ # Returns the hostname of the labeler service, if present.
59
+ #
60
+ # @api public
61
+ # @return [String, nil] hostname of the labeler endpoint URL
62
+
21
63
  def labeler_host
22
64
  labeler_endpoint&.then { |x| URI(x).host }
23
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DIDKit
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: didkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
@@ -52,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  requirements: []
55
- rubygems_version: 4.0.1
55
+ rubygems_version: 4.0.3
56
56
  specification_version: 4
57
57
  summary: A library for handling Distributed ID (DID) identifiers used in Bluesky AT
58
58
  Protocol