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 +7 -0
- data/lib/rest_easy/attribute.rb +71 -0
- data/lib/rest_easy/auth/basic.rb +23 -0
- data/lib/rest_easy/auth/null.rb +17 -0
- data/lib/rest_easy/auth/psk.rb +23 -0
- data/lib/rest_easy/auth.rb +6 -0
- data/lib/rest_easy/conventions.rb +70 -0
- data/lib/rest_easy/meta.rb +32 -0
- data/lib/rest_easy/refinements.rb +17 -0
- data/lib/rest_easy/resource.rb +747 -0
- data/lib/rest_easy/settings.rb +14 -0
- data/lib/rest_easy/version.rb +5 -0
- data/lib/rest_easy.rb +198 -0
- metadata +168 -0
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,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
|