ohauthcat 0.0.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.
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OhAuthCat
4
+ # Base of every generated model. A model wraps its JSON Hash, so unknown fields are kept and
5
+ # #to_h is always available as an escape hatch. Typed readers and writers use that Hash.
6
+ class Model
7
+ REGISTRY = {}
8
+
9
+ class << self
10
+ attr_reader :model_name, :fields, :required_fields
11
+
12
+ def model(name, required: [])
13
+ @model_name = name
14
+ @fields = {}
15
+ @required_fields = required.freeze
16
+ REGISTRY[name] = self
17
+ end
18
+
19
+ def field(member, json_name, desc)
20
+ @fields[json_name] = desc
21
+ define_method(member) { Model.decode(@json[json_name], desc) }
22
+ define_method(:"#{member}=") { |value| @json[json_name] = Model.encode(value) }
23
+ end
24
+
25
+ # Validates decoded JSON and wraps it.
26
+ def from_json(value)
27
+ Model.validate(value, "M#{model_name}", model_name, present: true)
28
+ new(value)
29
+ end
30
+
31
+ def wrap(hash)
32
+ new(hash)
33
+ end
34
+ end
35
+
36
+ def initialize(json = {})
37
+ @json = json.transform_keys(&:to_s)
38
+ end
39
+
40
+ # The underlying JSON Hash: every field, including ones this SDK version does not know.
41
+ def to_h
42
+ @json
43
+ end
44
+
45
+ def [](key)
46
+ @json[key.to_s]
47
+ end
48
+
49
+ def to_json(*args)
50
+ @json.to_json(*args)
51
+ end
52
+
53
+ def ==(other)
54
+ other.is_a?(Model) && other.class == self.class && other.to_h == @json
55
+ end
56
+
57
+ def inspect
58
+ "#<#{self.class.model_name} #{@json.keys.join(", ")}>"
59
+ end
60
+
61
+ def self.invalid(path)
62
+ ::OhAuthCat::Error.new("The response does not match the API schema at #{path}", code: "response_invalid")
63
+ end
64
+
65
+ def self.validate(value, desc, path, present: true)
66
+ nullable = desc.start_with?("?")
67
+ d = nullable ? desc[1..] : desc
68
+ if value.nil?
69
+ return if nullable || !present
70
+
71
+ raise invalid(path)
72
+ end
73
+ case d[0]
74
+ when "s" then raise invalid(path) unless value.is_a?(String)
75
+ when "i" then raise invalid(path) unless value.is_a?(Integer)
76
+ when "n" then raise invalid(path) unless value.is_a?(Numeric)
77
+ when "b" then raise invalid(path) unless [true, false].include?(value)
78
+ when "j" then nil
79
+ when "L"
80
+ raise invalid(path) unless value.is_a?(Array)
81
+
82
+ value.each_with_index { |item, i| validate(item, d[1..], "#{path}[#{i}]") }
83
+ when "D"
84
+ raise invalid(path) unless value.is_a?(Hash)
85
+
86
+ value.each { |k, v| validate(v, d[1..], "#{path}.#{k}") }
87
+ when "M"
88
+ klass = REGISTRY[d[1..]]
89
+ raise invalid(path) unless klass && value.is_a?(Hash)
90
+
91
+ klass.required_fields.each { |f| raise invalid("#{path}.#{f}") unless value.key?(f) }
92
+ klass.fields.each do |name, field_desc|
93
+ next unless value.key?(name)
94
+ # Optional fields may be null even when the schema does not say so.
95
+ next if value[name].nil? && !klass.required_fields.include?(name)
96
+
97
+ validate(value[name], field_desc, "#{path}.#{name}")
98
+ end
99
+ else
100
+ raise invalid(path)
101
+ end
102
+ end
103
+
104
+ def self.decode(value, desc)
105
+ return value if value.nil?
106
+
107
+ d = desc.delete_prefix("?")
108
+ case d[0]
109
+ when "n" then value.is_a?(Numeric) ? value.to_f : value
110
+ when "L" then value.is_a?(Array) ? value.map { |item| decode(item, d[1..]) } : value
111
+ when "D" then value.is_a?(Hash) ? value.transform_values { |item| decode(item, d[1..]) } : value
112
+ when "M"
113
+ klass = REGISTRY[d[1..]]
114
+ klass && value.is_a?(Hash) ? klass.wrap(value) : value
115
+ else value
116
+ end
117
+ end
118
+
119
+ # Converts typed values (models, arrays, hashes) back into plain JSON values.
120
+ def self.encode(value)
121
+ case value
122
+ when Model then value.to_h
123
+ when Hash then value.to_h { |k, v| [k.to_s, encode(v)] }
124
+ when Array then value.map { |item| encode(item) }
125
+ else value
126
+ end
127
+ end
128
+ end
129
+ end