rest-easy 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 45f897c2480954795efd3cae882ba4502ba38f7eeeb857f4a5a5ae897f9cc8f1
4
+ data.tar.gz: e7f42336f9d418c2c97b628cc5d393bdfd07472152c452ca60ebac770da02f49
5
+ SHA512:
6
+ metadata.gz: 7e2b2ad556be8737a7f85247e1d70a391d16bae3df56b6c9203ac3f746a6c54611218b18d34340d2b078a4f2b9eeb9c85ad15083e4cf75de07e8dc7c93033f45
7
+ data.tar.gz: b3fce6ba8f12d94e89df4b7325abc07d1ead9dd687b460cddd7df2c4264b21beeb1b85eba6770dc82a8894100be4d5d7b9f650b84b15d9fc6e474fc7b6d73744
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RestEasy
4
+ # Stores the definition of a single attribute declared via the Resource DSL.
5
+ class Attribute
6
+ attr_reader :model_name, :api_name, :type, :flags, :parse_block, :serialise_block, :source_fields, :target_fields
7
+
8
+ def initialize(model_name:, api_name:, type:, flags: [], parse_block: nil, serialise_block: nil, source_fields: [], target_fields: [])
9
+ @model_name = model_name.to_sym
10
+ @api_name = api_name.to_s
11
+ @type = type
12
+ @flags = Array(flags).map(&:to_sym)
13
+ @parse_block = parse_block
14
+ @serialise_block = serialise_block
15
+ @source_fields = source_fields
16
+ @target_fields = target_fields
17
+ end
18
+
19
+ def required?
20
+ @flags.include?(:required)
21
+ end
22
+
23
+ def optional?
24
+ @flags.include?(:optional)
25
+ end
26
+
27
+ def read_only?
28
+ @flags.include?(:read_only)
29
+ end
30
+
31
+ def key?
32
+ @flags.include?(:key)
33
+ end
34
+
35
+ def synthetic?
36
+ @flags.include?(:synthetic)
37
+ end
38
+
39
+ def coerce(value)
40
+ @type[value]
41
+ rescue Dry::Types::ConstraintError => e
42
+ raise RestEasy::ConstraintError.new(@model_name, value, e.message)
43
+ rescue Dry::Types::CoercionError => e
44
+ raise RestEasy::ConstraintError.new(@model_name, value, e.message)
45
+ end
46
+
47
+ def parse_value(*raw_values)
48
+ value = @parse_block ? @parse_block.call(*raw_values) : raw_values.first
49
+ coerce(value)
50
+ end
51
+
52
+ def serialise_value(*model_values)
53
+ if @serialise_block
54
+ @serialise_block.call(*model_values)
55
+ else
56
+ to_json_value(model_values.first)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def to_json_value(value)
63
+ case value
64
+ when ::String, ::Integer, ::Float, ::NilClass, true, false, ::Array, ::Hash
65
+ value
66
+ else
67
+ value.to_s
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module RestEasy
6
+ module Auth
7
+ # HTTP Basic authentication.
8
+ # Encodes username:password as Base64 and sets the Authorization header.
9
+ class Basic
10
+ def initialize(username:, password:)
11
+ @encoded = Base64.strict_encode64("#{username}:#{password}")
12
+ end
13
+
14
+ def apply(request)
15
+ request.headers["Authorization"] = "Basic #{@encoded}"
16
+ end
17
+
18
+ def on_rejected(response)
19
+ raise RestEasy::AuthenticationError, "Authentication failed: #{response.status}"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RestEasy
4
+ module Auth
5
+ # No-op auth for APIs that don't require authentication,
6
+ # or when auth is handled at the transport level (e.g. mTLS).
7
+ class Null
8
+ def apply(request)
9
+ # no-op
10
+ end
11
+
12
+ def on_rejected(response)
13
+ raise RestEasy::RequestError.new(response)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RestEasy
4
+ module Auth
5
+ # Pre-shared key (API key) authentication.
6
+ # Applies a static key as a request header.
7
+ class PSK
8
+ def initialize(api_key:, header_name: "Authorization", header_prefix: "Bearer")
9
+ @api_key = api_key
10
+ @header_name = header_name
11
+ @header_prefix = header_prefix
12
+ end
13
+
14
+ def apply(request)
15
+ request.headers[@header_name] = "#{@header_prefix} #{@api_key}"
16
+ end
17
+
18
+ def on_rejected(response)
19
+ raise RestEasy::RequestError.new(response)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RestEasy
4
+ module Auth
5
+ end
6
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RestEasy
4
+ module Conventions
5
+ class PascalCase
6
+ def parse(api_name)
7
+ # "DocumentNumber" → :document_number
8
+ api_name.to_s
9
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
10
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
11
+ .downcase
12
+ .to_sym
13
+ end
14
+
15
+ def serialise(model_name)
16
+ # :document_number → "DocumentNumber"
17
+ model_name.to_s
18
+ .split("_")
19
+ .map(&:capitalize)
20
+ .join
21
+ end
22
+ end
23
+
24
+ class CamelCase
25
+ def parse(api_name)
26
+ # "documentNumber" → :document_number
27
+ api_name.to_s
28
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
29
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
30
+ .downcase
31
+ .to_sym
32
+ end
33
+
34
+ def serialise(model_name)
35
+ # :document_number → "documentNumber"
36
+ parts = model_name.to_s.split("_")
37
+ (parts[0] + parts[1..].map(&:capitalize).join)
38
+ end
39
+ end
40
+
41
+ class SnakeCase
42
+ def parse(api_name)
43
+ # "document_number" → :document_number
44
+ api_name.to_s.to_sym
45
+ end
46
+
47
+ def serialise(model_name)
48
+ # :document_number → "document_number"
49
+ model_name.to_s
50
+ end
51
+ end
52
+
53
+ REGISTRY = {
54
+ PascalCase: PascalCase.new,
55
+ camelCase: CamelCase.new,
56
+ snake_case: SnakeCase.new
57
+ }.freeze
58
+
59
+ def self.resolve(convention)
60
+ case convention
61
+ when Symbol
62
+ REGISTRY.fetch(convention) do
63
+ raise ArgumentError, "Unknown convention: #{convention}. Available: #{REGISTRY.keys.join(', ')}"
64
+ end
65
+ else
66
+ convention # Custom object, pass through
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RestEasy
4
+ class Meta
5
+ def initialize(new_record: false, saved: false, **defaults)
6
+ @data = { new: new_record, saved: saved, **defaults }
7
+ end
8
+
9
+ def new?
10
+ @data[:new]
11
+ end
12
+
13
+ def saved?
14
+ @data[:saved]
15
+ end
16
+
17
+ def method_missing(name, *args)
18
+ key = name.to_s
19
+ if key.end_with?("=")
20
+ @data[key.chomp("=").to_sym] = args.first
21
+ elsif key.end_with?("?")
22
+ !!@data[key.chomp("?").to_sym]
23
+ else
24
+ @data[name.to_sym]
25
+ end
26
+ end
27
+
28
+ def respond_to_missing?(_name, _include_private = false)
29
+ true
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RestEasy
4
+ module Refinements
5
+ refine Symbol do
6
+ def <=>(other)
7
+ [self, other]
8
+ end
9
+ end
10
+
11
+ refine String do
12
+ def <=>(other)
13
+ [self, other]
14
+ end
15
+ end
16
+ end
17
+ end