antd 0.1.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.
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Antd
6
+ # Auto-discovers the antd daemon by reading the +daemon.port+ file that antd
7
+ # writes on startup.
8
+ #
9
+ # The file contains up to three lines: REST port (line 1), gRPC port (line 2),
10
+ # and optionally the daemon PID (line 3).
11
+ #
12
+ # If a PID is present and the process is no longer alive, the port file is
13
+ # considered stale and discovery returns empty.
14
+ #
15
+ # Port file location is platform-specific:
16
+ # - Windows: %APPDATA%\ant\sdk\daemon.port
17
+ # - macOS: ~/Library/Application Support/ant/sdk/daemon.port
18
+ # - Linux: $XDG_DATA_HOME/ant/sdk/daemon.port or ~/.local/share/ant/sdk/daemon.port
19
+ #
20
+ # The +sdk+ subdirectory keeps antd's port file separate from the ant-node
21
+ # daemon, which writes to the same +ant+ umbrella dir.
22
+ module Discover
23
+ PORT_FILE_NAME = "daemon.port"
24
+ DATA_DIR_NAME = "ant"
25
+ SDK_SUBDIR_NAME = "sdk"
26
+
27
+ # Reads the daemon.port file and returns the REST base URL
28
+ # (e.g. "http://127.0.0.1:8082").
29
+ #
30
+ # @return [String] the URL, or "" if the port file is not found
31
+ def self.daemon_url
32
+ rest, _ = read_port_file
33
+ return "" if rest == 0
34
+
35
+ "http://127.0.0.1:#{rest}"
36
+ end
37
+
38
+ # Reads the daemon.port file and returns the gRPC target
39
+ # (e.g. "127.0.0.1:50051").
40
+ #
41
+ # @return [String] the target, or "" if the port file is not found
42
+ def self.grpc_target
43
+ _, grpc = read_port_file
44
+ return "" if grpc == 0
45
+
46
+ "127.0.0.1:#{grpc}"
47
+ end
48
+
49
+ # @api private
50
+ def self.read_port_file
51
+ dir = data_dir
52
+ return [0, 0] if dir.empty?
53
+
54
+ path = File.join(dir, PORT_FILE_NAME)
55
+ return [0, 0] unless File.exist?(path)
56
+
57
+ lines = File.read(path).strip.split("\n")
58
+ rest_port = parse_port(lines[0])
59
+ grpc_port = parse_port(lines[1])
60
+ pid = parse_pid(lines[2])
61
+
62
+ return [0, 0] if pid > 0 && !process_alive?(pid)
63
+
64
+ [rest_port, grpc_port]
65
+ rescue StandardError
66
+ [0, 0]
67
+ end
68
+
69
+ # @api private
70
+ def self.parse_port(str)
71
+ return 0 if str.nil?
72
+
73
+ s = str.strip
74
+ return 0 unless s.match?(/\A\d+\z/)
75
+
76
+ n = s.to_i
77
+ (n > 0 && n <= 65535) ? n : 0
78
+ end
79
+
80
+ # @api private
81
+ def self.data_dir
82
+ host_os = RbConfig::CONFIG["host_os"]
83
+
84
+ case host_os
85
+ when /mswin|mingw|cygwin/
86
+ appdata = ENV["APPDATA"]
87
+ return "" if appdata.nil? || appdata.empty?
88
+
89
+ File.join(appdata, DATA_DIR_NAME, SDK_SUBDIR_NAME)
90
+ when /darwin/
91
+ home = ENV["HOME"]
92
+ return "" if home.nil? || home.empty?
93
+
94
+ File.join(home, "Library", "Application Support", DATA_DIR_NAME, SDK_SUBDIR_NAME)
95
+ else
96
+ xdg = ENV["XDG_DATA_HOME"]
97
+ if xdg && !xdg.empty?
98
+ File.join(xdg, DATA_DIR_NAME, SDK_SUBDIR_NAME)
99
+ else
100
+ home = ENV["HOME"]
101
+ return "" if home.nil? || home.empty?
102
+
103
+ File.join(home, ".local", "share", DATA_DIR_NAME, SDK_SUBDIR_NAME)
104
+ end
105
+ end
106
+ end
107
+
108
+ # @api private
109
+ def self.parse_pid(str)
110
+ return 0 if str.nil?
111
+
112
+ s = str.strip
113
+ return 0 unless s.match?(/\A\d+\z/)
114
+
115
+ n = s.to_i
116
+ n > 0 ? n : 0
117
+ end
118
+
119
+ # @api private
120
+ def self.process_alive?(pid)
121
+ Process.kill(0, pid)
122
+ true
123
+ rescue Errno::ESRCH
124
+ false
125
+ rescue Errno::EPERM
126
+ # Process exists but we lack permission to signal it — still alive.
127
+ true
128
+ end
129
+
130
+ private_class_method :read_port_file, :parse_port, :parse_pid, :process_alive?, :data_dir
131
+ end
132
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Antd
4
+ # Base error type for all antd errors.
5
+ class AntdError < StandardError
6
+ attr_reader :status_code
7
+
8
+ def initialize(message, status_code:)
9
+ @status_code = status_code
10
+ super("antd error #{status_code}: #{message}")
11
+ end
12
+ end
13
+
14
+ # Invalid request parameters (HTTP 400).
15
+ class BadRequestError < AntdError
16
+ def initialize(message) = super(message, status_code: 400)
17
+ end
18
+
19
+ # Insufficient funds or payment failure (HTTP 402).
20
+ class PaymentError < AntdError
21
+ def initialize(message) = super(message, status_code: 402)
22
+ end
23
+
24
+ # Resource not found on the network (HTTP 404).
25
+ class NotFoundError < AntdError
26
+ def initialize(message) = super(message, status_code: 404)
27
+ end
28
+
29
+ # Resource already exists (HTTP 409).
30
+ class AlreadyExistsError < AntdError
31
+ def initialize(message) = super(message, status_code: 409)
32
+ end
33
+
34
+ # Version conflict or fork detected (HTTP 409).
35
+ class ForkError < AntdError
36
+ def initialize(message) = super(message, status_code: 409)
37
+ end
38
+
39
+ # Payload too large (HTTP 413).
40
+ class TooLargeError < AntdError
41
+ def initialize(message) = super(message, status_code: 413)
42
+ end
43
+
44
+ # Internal server error (HTTP 500).
45
+ class InternalError < AntdError
46
+ def initialize(message) = super(message, status_code: 500)
47
+ end
48
+
49
+ # Daemon cannot reach the network (HTTP 502).
50
+ class NetworkError < AntdError
51
+ def initialize(message) = super(message, status_code: 502)
52
+ end
53
+
54
+ # Service unavailable, e.g. wallet not configured (HTTP 503).
55
+ class ServiceUnavailableError < AntdError
56
+ def initialize(message) = super(message, status_code: 503)
57
+ end
58
+
59
+ # Returns the appropriate error type for an HTTP status code.
60
+ def self.error_for_status(code, message)
61
+ case code
62
+ when 400 then BadRequestError.new(message)
63
+ when 402 then PaymentError.new(message)
64
+ when 404 then NotFoundError.new(message)
65
+ when 409 then AlreadyExistsError.new(message)
66
+ when 413 then TooLargeError.new(message)
67
+ when 500 then InternalError.new(message)
68
+ when 502 then NetworkError.new(message)
69
+ when 503 then ServiceUnavailableError.new(message)
70
+ else AntdError.new(message, status_code: code)
71
+ end
72
+ end
73
+ end