noresponse 2.0.17

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.
Files changed (231) hide show
  1. checksums.yaml +7 -0
  2. data/lib/crystalline/enum.rbi +17 -0
  3. data/lib/crystalline/metadata_fields.rb +263 -0
  4. data/lib/crystalline/module.rb +186 -0
  5. data/lib/crystalline/types.rb +168 -0
  6. data/lib/crystalline/utils.rb +67 -0
  7. data/lib/crystalline.rb +11 -0
  8. data/lib/no_response/assessments.rb +1252 -0
  9. data/lib/no_response/auth.rb +263 -0
  10. data/lib/no_response/issued_credential.rb +21 -0
  11. data/lib/no_response/keys.rb +318 -0
  12. data/lib/no_response/late_results.rb +301 -0
  13. data/lib/no_response/models/callbacks.rb +10 -0
  14. data/lib/no_response/models/errors/apistatuserror.rb +41 -0
  15. data/lib/no_response/models/errors/apistatuserror.rbi +30 -0
  16. data/lib/no_response/models/errors/authenticationerror.rb +108 -0
  17. data/lib/no_response/models/errors/authenticationerror.rbi +56 -0
  18. data/lib/no_response/models/errors/badrequesterror.rb +108 -0
  19. data/lib/no_response/models/errors/badrequesterror.rbi +56 -0
  20. data/lib/no_response/models/errors/conflicterror.rb +108 -0
  21. data/lib/no_response/models/errors/conflicterror.rbi +56 -0
  22. data/lib/no_response/models/errors/notfounderror.rb +108 -0
  23. data/lib/no_response/models/errors/notfounderror.rbi +56 -0
  24. data/lib/no_response/models/errors/permissiondeniederror.rb +108 -0
  25. data/lib/no_response/models/errors/permissiondeniederror.rbi +56 -0
  26. data/lib/no_response/models/errors/ratelimiterror.rb +108 -0
  27. data/lib/no_response/models/errors/ratelimiterror.rbi +56 -0
  28. data/lib/no_response/models/errors.rb +16 -0
  29. data/lib/no_response/models/operations/createassessment_request.rb +36 -0
  30. data/lib/no_response/models/operations/createassessment_request.rbi +20 -0
  31. data/lib/no_response/models/operations/createassessment_response.rb +32 -0
  32. data/lib/no_response/models/operations/createassessment_response.rbi +20 -0
  33. data/lib/no_response/models/operations/getassessment_request.rb +41 -0
  34. data/lib/no_response/models/operations/getassessment_request.rbi +20 -0
  35. data/lib/no_response/models/operations/getassessment_response.rb +32 -0
  36. data/lib/no_response/models/operations/getassessment_response.rbi +20 -0
  37. data/lib/no_response/models/operations/queryaccountassessments_response.rb +44 -0
  38. data/lib/no_response/models/operations/queryaccountassessments_response.rbi +20 -0
  39. data/lib/no_response/models/operations/recordassessmentoutcome_request.rb +52 -0
  40. data/lib/no_response/models/operations/recordassessmentoutcome_request.rbi +26 -0
  41. data/lib/no_response/models/operations/recordassessmentoutcome_response.rb +32 -0
  42. data/lib/no_response/models/operations/recordassessmentoutcome_response.rbi +20 -0
  43. data/lib/no_response/models/operations/rotateapikey_response.rb +32 -0
  44. data/lib/no_response/models/operations/rotateapikey_response.rbi +20 -0
  45. data/lib/no_response/models/operations.rb +17 -0
  46. data/lib/no_response/models/shared/alternate_name.rb +23 -0
  47. data/lib/no_response/models/shared/alternate_name.rbi +9 -0
  48. data/lib/no_response/models/shared/alternate_name_union.rb +23 -0
  49. data/lib/no_response/models/shared/alternate_name_union.rbi +9 -0
  50. data/lib/no_response/models/shared/assessment.rb +235 -0
  51. data/lib/no_response/models/shared/assessment.rbi +116 -0
  52. data/lib/no_response/models/shared/assessmentlist.rb +78 -0
  53. data/lib/no_response/models/shared/assessmentlist.rbi +38 -0
  54. data/lib/no_response/models/shared/assessmentoutcome.rb +61 -0
  55. data/lib/no_response/models/shared/assessmentoutcome.rbi +32 -0
  56. data/lib/no_response/models/shared/assessmentoutcomerequest.rb +71 -0
  57. data/lib/no_response/models/shared/assessmentoutcomerequest.rbi +32 -0
  58. data/lib/no_response/models/shared/assessmentquery.rb +70 -0
  59. data/lib/no_response/models/shared/assessmentquery.rbi +32 -0
  60. data/lib/no_response/models/shared/assessmentrequest.rb +125 -0
  61. data/lib/no_response/models/shared/assessmentrequest.rbi +62 -0
  62. data/lib/no_response/models/shared/assessmentrequestclientcontext.rb +125 -0
  63. data/lib/no_response/models/shared/assessmentrequestclientcontext.rbi +62 -0
  64. data/lib/no_response/models/shared/assessmentrequestidentifiers.rb +74 -0
  65. data/lib/no_response/models/shared/assessmentrequestidentifiers.rbi +38 -0
  66. data/lib/no_response/models/shared/assessmentrequestissuedcredential.rb +33 -0
  67. data/lib/no_response/models/shared/assessmentrequestissuedcredential.rbi +14 -0
  68. data/lib/no_response/models/shared/assessmentrequestpolicycontext.rb +46 -0
  69. data/lib/no_response/models/shared/assessmentrequestpolicycontext.rbi +20 -0
  70. data/lib/no_response/models/shared/assessmenttiming.rb +41 -0
  71. data/lib/no_response/models/shared/assessmenttiming.rbi +20 -0
  72. data/lib/no_response/models/shared/assurancelevel.rb +18 -0
  73. data/lib/no_response/models/shared/assurancelevel.rbi +9 -0
  74. data/lib/no_response/models/shared/authcontext.rb +285 -0
  75. data/lib/no_response/models/shared/authcontext.rbi +152 -0
  76. data/lib/no_response/models/shared/authcontextmfa.rb +55 -0
  77. data/lib/no_response/models/shared/authcontextmfa.rbi +26 -0
  78. data/lib/no_response/models/shared/authenticationerrorcode.rb +18 -0
  79. data/lib/no_response/models/shared/authenticationerrorcode.rbi +9 -0
  80. data/lib/no_response/models/shared/badrequesterrorcode.rb +20 -0
  81. data/lib/no_response/models/shared/badrequesterrorcode.rbi +9 -0
  82. data/lib/no_response/models/shared/band.rb +21 -0
  83. data/lib/no_response/models/shared/band.rbi +9 -0
  84. data/lib/no_response/models/shared/breach.rb +108 -0
  85. data/lib/no_response/models/shared/breach.rbi +56 -0
  86. data/lib/no_response/models/shared/breachexposed.rb +28 -0
  87. data/lib/no_response/models/shared/breachexposed.rbi +9 -0
  88. data/lib/no_response/models/shared/breachkind.rb +24 -0
  89. data/lib/no_response/models/shared/breachkind.rbi +9 -0
  90. data/lib/no_response/models/shared/breachselector.rb +20 -0
  91. data/lib/no_response/models/shared/breachselector.rbi +9 -0
  92. data/lib/no_response/models/shared/breachsummary.rb +109 -0
  93. data/lib/no_response/models/shared/breachsummary.rbi +56 -0
  94. data/lib/no_response/models/shared/confidence.rb +21 -0
  95. data/lib/no_response/models/shared/confidence.rbi +9 -0
  96. data/lib/no_response/models/shared/conflicterrorcode.rb +19 -0
  97. data/lib/no_response/models/shared/conflicterrorcode.rbi +9 -0
  98. data/lib/no_response/models/shared/consistency.rb +85 -0
  99. data/lib/no_response/models/shared/consistency.rbi +32 -0
  100. data/lib/no_response/models/shared/consistencyvalue.rb +21 -0
  101. data/lib/no_response/models/shared/consistencyvalue.rbi +9 -0
  102. data/lib/no_response/models/shared/country.rb +20 -0
  103. data/lib/no_response/models/shared/country.rbi +9 -0
  104. data/lib/no_response/models/shared/email.rb +23 -0
  105. data/lib/no_response/models/shared/email.rbi +9 -0
  106. data/lib/no_response/models/shared/email_union.rb +23 -0
  107. data/lib/no_response/models/shared/email_union.rbi +9 -0
  108. data/lib/no_response/models/shared/failurereason.rb +24 -0
  109. data/lib/no_response/models/shared/failurereason.rbi +9 -0
  110. data/lib/no_response/models/shared/flow.rb +18 -0
  111. data/lib/no_response/models/shared/flow.rbi +9 -0
  112. data/lib/no_response/models/shared/freshness.rb +20 -0
  113. data/lib/no_response/models/shared/freshness.rbi +9 -0
  114. data/lib/no_response/models/shared/hookpoint.rb +19 -0
  115. data/lib/no_response/models/shared/hookpoint.rbi +9 -0
  116. data/lib/no_response/models/shared/identifierkind.rb +21 -0
  117. data/lib/no_response/models/shared/identifierkind.rbi +9 -0
  118. data/lib/no_response/models/shared/incompletereason.rb +23 -0
  119. data/lib/no_response/models/shared/incompletereason.rbi +9 -0
  120. data/lib/no_response/models/shared/incompletesource.rb +53 -0
  121. data/lib/no_response/models/shared/incompletesource.rbi +20 -0
  122. data/lib/no_response/models/shared/ipsummary.rb +61 -0
  123. data/lib/no_response/models/shared/ipsummary.rbi +26 -0
  124. data/lib/no_response/models/shared/ipsummarytype.rb +24 -0
  125. data/lib/no_response/models/shared/ipsummarytype.rbi +9 -0
  126. data/lib/no_response/models/shared/keyrotation.rb +79 -0
  127. data/lib/no_response/models/shared/keyrotation.rbi +44 -0
  128. data/lib/no_response/models/shared/keyrotationrequest.rb +33 -0
  129. data/lib/no_response/models/shared/keyrotationrequest.rbi +14 -0
  130. data/lib/no_response/models/shared/lane.rb +21 -0
  131. data/lib/no_response/models/shared/lane.rbi +9 -0
  132. data/lib/no_response/models/shared/linetype.rb +22 -0
  133. data/lib/no_response/models/shared/linetype.rbi +9 -0
  134. data/lib/no_response/models/shared/location.rb +23 -0
  135. data/lib/no_response/models/shared/location.rbi +9 -0
  136. data/lib/no_response/models/shared/location_union.rb +23 -0
  137. data/lib/no_response/models/shared/location_union.rbi +9 -0
  138. data/lib/no_response/models/shared/name_candidate.rb +23 -0
  139. data/lib/no_response/models/shared/name_candidate.rbi +9 -0
  140. data/lib/no_response/models/shared/name_candidate_union.rb +23 -0
  141. data/lib/no_response/models/shared/name_candidate_union.rbi +9 -0
  142. data/lib/no_response/models/shared/notfounderrorcode.rb +18 -0
  143. data/lib/no_response/models/shared/notfounderrorcode.rbi +9 -0
  144. data/lib/no_response/models/shared/outcome.rb +23 -0
  145. data/lib/no_response/models/shared/outcome.rbi +9 -0
  146. data/lib/no_response/models/shared/permissiondeniederrorcode.rb +18 -0
  147. data/lib/no_response/models/shared/permissiondeniederrorcode.rbi +9 -0
  148. data/lib/no_response/models/shared/phoneenrichment.rb +32 -0
  149. data/lib/no_response/models/shared/phoneenrichment.rbi +14 -0
  150. data/lib/no_response/models/shared/phoneenrichmentfields.rb +451 -0
  151. data/lib/no_response/models/shared/phoneenrichmentfields.rbi +212 -0
  152. data/lib/no_response/models/shared/platform.rb +18 -0
  153. data/lib/no_response/models/shared/platform.rbi +9 -0
  154. data/lib/no_response/models/shared/probleminvalidparam.rb +48 -0
  155. data/lib/no_response/models/shared/probleminvalidparam.rbi +26 -0
  156. data/lib/no_response/models/shared/ratelimiterrorcode.rb +18 -0
  157. data/lib/no_response/models/shared/ratelimiterrorcode.rbi +9 -0
  158. data/lib/no_response/models/shared/reasoncode.rb +52 -0
  159. data/lib/no_response/models/shared/reasoncode.rbi +9 -0
  160. data/lib/no_response/models/shared/reasonidentifier.rb +23 -0
  161. data/lib/no_response/models/shared/reasonidentifier.rbi +9 -0
  162. data/lib/no_response/models/shared/recommendation.rb +20 -0
  163. data/lib/no_response/models/shared/recommendation.rbi +9 -0
  164. data/lib/no_response/models/shared/responsemode.rb +16 -0
  165. data/lib/no_response/models/shared/responsemode.rbi +9 -0
  166. data/lib/no_response/models/shared/risk.rb +171 -0
  167. data/lib/no_response/models/shared/risk.rbi +80 -0
  168. data/lib/no_response/models/shared/riskconfidence.rb +20 -0
  169. data/lib/no_response/models/shared/riskconfidence.rbi +9 -0
  170. data/lib/no_response/models/shared/riskcoverage.rb +49 -0
  171. data/lib/no_response/models/shared/riskcoverage.rbi +26 -0
  172. data/lib/no_response/models/shared/riskdetails.rb +55 -0
  173. data/lib/no_response/models/shared/riskdetails.rbi +26 -0
  174. data/lib/no_response/models/shared/riskdetailsbreachname.rb +40 -0
  175. data/lib/no_response/models/shared/riskdetailsbreachname.rbi +20 -0
  176. data/lib/no_response/models/shared/riskdetailsphonename.rb +40 -0
  177. data/lib/no_response/models/shared/riskdetailsphonename.rbi +20 -0
  178. data/lib/no_response/models/shared/riskreason.rb +67 -0
  179. data/lib/no_response/models/shared/riskreason.rbi +26 -0
  180. data/lib/no_response/models/shared/risksummary.rb +73 -0
  181. data/lib/no_response/models/shared/risksummary.rbi +38 -0
  182. data/lib/no_response/models/shared/risksummaryemail.rb +55 -0
  183. data/lib/no_response/models/shared/risksummaryemail.rbi +26 -0
  184. data/lib/no_response/models/shared/risksummaryphone.rb +69 -0
  185. data/lib/no_response/models/shared/risksummaryphone.rbi +32 -0
  186. data/lib/no_response/models/shared/risksummaryusername.rb +41 -0
  187. data/lib/no_response/models/shared/risksummaryusername.rbi +20 -0
  188. data/lib/no_response/models/shared/scope.rb +21 -0
  189. data/lib/no_response/models/shared/scope.rbi +9 -0
  190. data/lib/no_response/models/shared/security.rb +32 -0
  191. data/lib/no_response/models/shared/security.rbi +14 -0
  192. data/lib/no_response/models/shared/signal.rb +149 -0
  193. data/lib/no_response/models/shared/signal.rbi +68 -0
  194. data/lib/no_response/models/shared/signaloutcome.rb +22 -0
  195. data/lib/no_response/models/shared/signaloutcome.rbi +9 -0
  196. data/lib/no_response/models/shared/social_profile.rb +23 -0
  197. data/lib/no_response/models/shared/social_profile.rbi +9 -0
  198. data/lib/no_response/models/shared/social_profile_union.rb +23 -0
  199. data/lib/no_response/models/shared/social_profile_union.rbi +9 -0
  200. data/lib/no_response/models/shared/status.rb +20 -0
  201. data/lib/no_response/models/shared/status.rbi +9 -0
  202. data/lib/no_response/models/shared/timezone.rb +23 -0
  203. data/lib/no_response/models/shared/timezone.rbi +9 -0
  204. data/lib/no_response/models/shared/timezone_union.rb +23 -0
  205. data/lib/no_response/models/shared/timezone_union.rbi +9 -0
  206. data/lib/no_response/models/shared/transport.rb +17 -0
  207. data/lib/no_response/models/shared/transport.rbi +9 -0
  208. data/lib/no_response/models/shared/website.rb +23 -0
  209. data/lib/no_response/models/shared/website.rbi +9 -0
  210. data/lib/no_response/models/shared/website_union.rb +23 -0
  211. data/lib/no_response/models/shared/website_union.rbi +9 -0
  212. data/lib/no_response/models/shared.rb +92 -0
  213. data/lib/no_response/noresponseclient.rb +110 -0
  214. data/lib/no_response/sdk_hooks/hooks.rb +97 -0
  215. data/lib/no_response/sdk_hooks/idempotency_key_hook.rb +25 -0
  216. data/lib/no_response/sdk_hooks/registration.rb +43 -0
  217. data/lib/no_response/sdk_hooks/types.rb +178 -0
  218. data/lib/no_response/sdkconfiguration.rb +107 -0
  219. data/lib/no_response/sdkconfiguration.rbi +83 -0
  220. data/lib/no_response/utils/forms.rb +230 -0
  221. data/lib/no_response/utils/headers.rb +80 -0
  222. data/lib/no_response/utils/query_params.rb +165 -0
  223. data/lib/no_response/utils/request_bodies.rb +77 -0
  224. data/lib/no_response/utils/retries.rb +137 -0
  225. data/lib/no_response/utils/security.rb +156 -0
  226. data/lib/no_response/utils/url.rb +122 -0
  227. data/lib/no_response/utils/utils.rb +243 -0
  228. data/lib/no_response/wait.rb +66 -0
  229. data/lib/no_response/webhooks.rb +177 -0
  230. data/lib/noresponse.rb +25 -0
  231. metadata +501 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 11bfa2970f31bcc0378fb41e28e49ef1110deabe3465de79f3ade73edc0d4925
4
+ data.tar.gz: 18e90777775e84f17ad88fe7ac513890e81bb8d9e94da0d9f32acccfff6ae4c4
5
+ SHA512:
6
+ metadata.gz: 47b473b6bd4a15e0ecd98201a5d091cb79a1132e2f994f98e596ac53a2f2c5a7f28906042736f9429b6738959cbc68ee67bddbce04b595a0849d6f7bcd815513
7
+ data.tar.gz: 40db1e2297e6b58cddf46fea5ef5ae3fbdb21942991367592c47182985e55e6d34e8296737c2027bf699b0fc33ab8bd63c876d38b3b6e4b92e9b7267e4a05004
@@ -0,0 +1,17 @@
1
+ # typed: true
2
+
3
+ module Crystalline
4
+ module Enum
5
+ mixes_in_class_methods(ClassMethods)
6
+
7
+ module ClassMethods
8
+ def enums(&blk); end
9
+ def open!; end
10
+ def open?; end
11
+ def deserialize(val); end
12
+ end
13
+
14
+ def serialize; end
15
+ def known?; end
16
+ end
17
+ end
@@ -0,0 +1,263 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Crystalline
5
+ module MetadataFields
6
+
7
+ class Field
8
+ attr_accessor :name, :type, :metadata
9
+
10
+ def initialize(name, type, metadata)
11
+ @name = name
12
+ @type = type
13
+ @metadata = metadata
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def fields
20
+ @__fields__ = [] if @__fields__.nil?
21
+
22
+ @__fields__
23
+ end
24
+
25
+ def field(field_name, type, metadata = {})
26
+ attr_accessor field_name
27
+
28
+ fields << Field.new(field_name, type, metadata)
29
+ end
30
+
31
+ def field_augmented?
32
+ true
33
+ end
34
+
35
+ def unmarshal_single(field_type, value, format_metadata = nil)
36
+ decoder = format_metadata.fetch(:decoder, nil)
37
+
38
+ # Delegate complex Crystalline types to unmarshal_json
39
+ if field_type.is_a?(Crystalline::DiscriminatedUnion) ||
40
+ Crystalline::Utils.arr?(field_type) ||
41
+ Crystalline::Utils.hash?(field_type) ||
42
+ Crystalline::Utils.union?(field_type)
43
+ return Crystalline.unmarshal_json(value, field_type)
44
+ elsif field_type.instance_of?(Class) && field_type.include?(::Crystalline::MetadataFields)
45
+ return field_type.from_dict(value)
46
+ elsif field_type.to_s == 'Date'
47
+ return Date.parse(value)
48
+ elsif field_type.to_s == 'DateTime'
49
+ return DateTime.parse(value)
50
+ elsif field_type.to_s == 'Object'
51
+ if value.is_a?(::String)
52
+ trimmed = value.lstrip
53
+ if trimmed.start_with?('{') || trimmed.start_with?('[')
54
+ # rubocop:disable Lint/SuppressedException
55
+ begin
56
+ value = JSON.parse(value)
57
+ rescue TypeError, JSON::ParserError
58
+ end
59
+ # rubocop:enable Lint/SuppressedException
60
+ end
61
+ end
62
+ return value
63
+ elsif field_type.to_s == 'Float'
64
+ return value.to_f
65
+
66
+ end
67
+ if decoder.nil?
68
+ value
69
+ else
70
+ decoder.call(value)
71
+ end
72
+ end
73
+
74
+ def from_dict(d)
75
+ to_build = {}
76
+
77
+ # Collect lookup keys for regular fields so we can identify additional properties later
78
+ known_keys = {}
79
+ additional_props_fields = []
80
+ fields.each do |field|
81
+ format_metadata = field.metadata.fetch(:format_json, {})
82
+ if format_metadata.fetch(:additional_properties, false)
83
+ additional_props_fields << field
84
+ else
85
+ lookup = format_metadata.fetch(:letter_case, nil).call
86
+ known_keys[lookup] = true
87
+ end
88
+ end
89
+
90
+ # Process regular fields
91
+ (fields - additional_props_fields).each do |field|
92
+ key = field.name
93
+ format_metadata = field.metadata.fetch(:format_json, {})
94
+ field_type = field.type
95
+
96
+ lookup = format_metadata.fetch(:letter_case, nil).call
97
+ value = d[lookup]
98
+ if ::Crystalline::Utils.nilable? field_type
99
+ if value == 'null'
100
+ to_build[key] = nil
101
+ next
102
+ end
103
+ field_type = ::Crystalline::Utils.nilable_of(field_type)
104
+ end
105
+
106
+ # If field is not nilable, and the value is not in the dict, raise a KeyError
107
+ raise KeyError, "key #{lookup} not found in hash" if value.nil? && !::Crystalline::Utils.nilable?(field.type)
108
+ # If field is nilable, and the value is not in the dict, just move to the next field
109
+ next if value.nil?
110
+
111
+ if field_type.is_a?(Crystalline::DiscriminatedUnion)
112
+ to_build[key] = field_type.parse(value)
113
+ elsif Crystalline::Utils.arr? field_type
114
+ inner_type = Crystalline::Utils.arr_of(field_type)
115
+ unmarshalled_array = value.map { |f| unmarshal_single(inner_type, f, format_metadata) }
116
+ to_build[key] = unmarshalled_array
117
+ elsif Crystalline::Utils.hash? field_type
118
+ val_type = Crystalline::Utils.hash_of(field_type)
119
+
120
+ # rubocop:disable Style/HashTransformValues
121
+ unmarshalled_hash = value.map { |k, v| [k, unmarshal_single(val_type, v, format_metadata)] }.to_h
122
+ # rubocop:enable Style/HashTransformValues
123
+ to_build[key] = unmarshalled_hash
124
+ elsif Crystalline::Utils.union? field_type
125
+ discriminator = field.metadata.fetch(:discriminator, nil)
126
+ discriminator_mapping = field.metadata.fetch(:discriminator_mapping, nil)
127
+ if !discriminator.nil?
128
+ discriminator_value = value.fetch(discriminator)
129
+ if !discriminator_mapping.nil?
130
+ # Use explicit mapping from discriminator value to type
131
+ type_to_deserialize = discriminator_mapping[discriminator_value]
132
+ else
133
+ # Fallback: try to match discriminator value against type name
134
+ type_to_deserialize = Crystalline::Utils.get_union_types(field_type).find { |t| t.name.split('::').last == discriminator_value }
135
+ end
136
+ to_build[key] = Crystalline.unmarshal_json(value, type_to_deserialize)
137
+ else
138
+ union_types = Crystalline::Utils.get_union_types(field_type)
139
+ union_types = union_types.sort_by { |klass| Crystalline.non_nilable_attr_count(klass) }
140
+
141
+ if Crystalline.union_strategy == :populated_fields
142
+ to_build[key] = Crystalline.unmarshal_union_populated_fields(value, union_types)
143
+ else
144
+ union_types.each do |union_type|
145
+ begin
146
+ to_build[key] = Crystalline.unmarshal_json(value, union_type)
147
+ rescue TypeError
148
+ next
149
+ rescue NoMethodError
150
+ next
151
+ rescue KeyError
152
+ next
153
+ end
154
+ break
155
+ end
156
+ end
157
+ end
158
+ elsif field_type.instance_of?(Class) && field_type.include?(::Crystalline::MetadataFields)
159
+ to_build[key] = Crystalline.unmarshal_json(value, field_type)
160
+ else
161
+ to_build[key] = unmarshal_single(field_type, value, format_metadata)
162
+ end
163
+ end
164
+
165
+ # Process additional properties fields: collect remaining keys from the dict
166
+ additional_props_fields.each do |field|
167
+ key = field.name
168
+ format_metadata = field.metadata.fetch(:format_json, {})
169
+ field_type = field.type
170
+
171
+ remaining = d.reject { |k, _| known_keys.key?(k) }
172
+ if remaining.empty?
173
+ next if ::Crystalline::Utils.nilable?(field.type)
174
+
175
+ raise KeyError, 'no additional properties found in hash'
176
+ end
177
+
178
+ inner_field_type = field_type
179
+ inner_field_type = ::Crystalline::Utils.nilable_of(inner_field_type) if ::Crystalline::Utils.nilable?(inner_field_type)
180
+ if Crystalline::Utils.hash?(inner_field_type)
181
+ val_type = Crystalline::Utils.hash_of(inner_field_type)
182
+ # rubocop:disable Style/HashTransformValues
183
+ to_build[key] = remaining.map { |k, v| [k, unmarshal_single(val_type, v, format_metadata)] }.to_h
184
+ # rubocop:enable Style/HashTransformValues
185
+ else
186
+ to_build[key] = remaining
187
+ end
188
+ end
189
+
190
+ new(**to_build)
191
+ end
192
+ end
193
+
194
+ def initialize
195
+ instance = allocate
196
+ fields.each { |mod| instance.singleton_class.include(mod) }
197
+ instance.send(:initialize)
198
+ end
199
+
200
+ def self.included(base)
201
+ base.extend(ClassMethods)
202
+ end
203
+
204
+ def fields
205
+ self.class.fields
206
+ end
207
+
208
+ def field(field_name)
209
+ fields.each do |f|
210
+ return f if f.name == field_name
211
+ end
212
+ end
213
+
214
+ def marshal_single(field)
215
+ if field.class.include? ::Crystalline::MetadataFields
216
+ field.to_dict
217
+ else
218
+ if ::Crystalline.needs_string_conversion field
219
+ ::Crystalline.json_encode field
220
+ else
221
+ field
222
+ end
223
+ end
224
+ end
225
+
226
+ def to_dict
227
+ result = {}
228
+ fields.sort_by(&:name).each do |field|
229
+ format_json_meta = field.metadata[:format_json]
230
+ required = !format_json_meta.nil? && format_json_meta.include?(:required)
231
+ is_additional_props = !format_json_meta.nil? && format_json_meta.fetch(:additional_properties, false)
232
+
233
+ if !format_json_meta.nil? && format_json_meta.include?(:letter_case)
234
+ key = format_json_meta[:letter_case].call(field.name)
235
+ else
236
+ key = field.name
237
+ end
238
+
239
+ f = send(field.name)
240
+ next if f.nil? && !required
241
+ result[key] = nil if f.nil? && required
242
+
243
+ # Flatten additional properties into the parent object
244
+ if is_additional_props && f.is_a?(::Hash)
245
+ f.each { |k, v| result[k] = marshal_single(v) }
246
+ elsif f.is_a? ::Array
247
+ result[key] = f.map { |o| marshal_single(o) }
248
+ elsif f.is_a? ::Hash
249
+ result[key] = f.transform_values { |v| marshal_single(v) }
250
+ else
251
+ result[key] = marshal_single(f)
252
+ end
253
+ end
254
+ result
255
+ end
256
+
257
+ def to_json(*args)
258
+ JSON.generate(to_dict, *args)
259
+ end
260
+ end
261
+
262
+
263
+ end
@@ -0,0 +1,186 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Crystalline
5
+ @union_strategy = :populated_fields
6
+
7
+ class << self
8
+ attr_accessor :union_strategy
9
+ end
10
+
11
+ def self.to_dict(complex)
12
+ if complex.is_a?(::Array)
13
+ complex.map { |v| Crystalline.to_dict(v) }
14
+ elsif complex.is_a?(::Hash)
15
+ complex.transform_values { |v| Crystalline.to_dict(v) }
16
+ elsif complex.respond_to?(:class) && complex.class.include?(::Crystalline::MetadataFields)
17
+ complex.to_dict
18
+ else
19
+ json_encode(complex, primitives: false)
20
+ end
21
+ end
22
+
23
+ def self.needs_string_conversion(val)
24
+ val.class.respond_to?(:enums) || val.is_a?(DateTime) || val.nil?
25
+ end
26
+
27
+ def self.to_json(complex)
28
+ JSON.dump(to_dict(complex))
29
+ end
30
+
31
+ def self.unmarshal_json(data, type)
32
+ if Crystalline::Utils.nilable?(type)
33
+ type = Crystalline::Utils.nilable_of(type)
34
+ end
35
+
36
+ if type.is_a?(Crystalline::DiscriminatedUnion)
37
+ type.parse(data)
38
+ elsif type.instance_of?(Class) && type.include?(::Crystalline::MetadataFields)
39
+ type.from_dict(data)
40
+ elsif Crystalline::Utils.union?(type)
41
+ union_types = Crystalline::Utils.get_union_types(type)
42
+ union_types = union_types.sort_by { |klass| Crystalline.non_nilable_attr_count(klass) }
43
+
44
+ if Crystalline.union_strategy == :populated_fields
45
+ Crystalline.unmarshal_union_populated_fields(data, union_types)
46
+ else
47
+ Crystalline.unmarshal_union_left_to_right(data, union_types)
48
+ end
49
+ elsif Crystalline::Utils.arr?(type)
50
+ data.map { |v| Crystalline.unmarshal_json(v, Crystalline::Utils.arr_of(type)) }
51
+ elsif Crystalline::Utils.hash?(type)
52
+ data.transform_values { |v| Crystalline.unmarshal_json(v, Crystalline::Utils.hash_of(type)) }
53
+ elsif Crystalline::Utils.nilable?(type) && data == "null"
54
+ nil
55
+ elsif Crystalline::Utils.boolean?(type)
56
+ Crystalline::Utils.to_boolean(data)
57
+ elsif type.is_a?(Class) && type < T::Enum
58
+ type.deserialize(data)
59
+ elsif type.is_a?(Class) && type.respond_to?(:enums) && type.respond_to?(:deserialize)
60
+ type.deserialize(data)
61
+ else
62
+ data
63
+ end
64
+ end
65
+
66
+ def self.json_encode(val, primitives: true)
67
+ if val.class.respond_to?(:enums)
68
+ val.serialize
69
+ elsif val.is_a?(DateTime)
70
+ val.strftime("%Y-%m-%dT%H:%M:%S.%NZ").sub(/(\.\d*[1-9])0+Z\z/, "\\1Z").sub(/\.0+Z\z/, "Z")
71
+ elsif val.nil?
72
+ nil
73
+ elsif primitives
74
+ val.to_s
75
+ else
76
+ val
77
+ end
78
+ end
79
+
80
+ def self.unmarshal_union_left_to_right(data, union_types)
81
+ union_types.each do |union_type|
82
+ return Crystalline.unmarshal_json(data, union_type)
83
+ rescue TypeError
84
+ next
85
+ rescue NoMethodError
86
+ next
87
+ rescue KeyError
88
+ next
89
+ end
90
+
91
+ nil
92
+ end
93
+
94
+ def self.unmarshal_union_populated_fields(data, union_types)
95
+ best_value = nil
96
+ best_matched = -1
97
+ best_inexact = 0
98
+ best_unmatched = 0
99
+
100
+ union_types.each do |union_type|
101
+ value = Crystalline.unmarshal_json(data, union_type)
102
+ matched, inexact, unmatched = count_matched_fields(value, data)
103
+
104
+ if best_value.nil? ||
105
+ matched > best_matched ||
106
+ (matched == best_matched && inexact < best_inexact) ||
107
+ (matched == best_matched && inexact == best_inexact && unmatched < best_unmatched)
108
+ best_value = value
109
+ best_matched = matched
110
+ best_inexact = inexact
111
+ best_unmatched = unmatched
112
+ end
113
+
114
+ rescue TypeError
115
+ next
116
+ rescue NoMethodError
117
+ next
118
+ rescue KeyError
119
+ next
120
+ end
121
+
122
+ best_value
123
+ end
124
+
125
+ def self.count_matched_fields(value, raw_data)
126
+ matched = 0
127
+ inexact = 0
128
+ unmatched = 0
129
+
130
+ if value.is_a?(Crystalline::Unknown)
131
+ return [0, 0, 0]
132
+ elsif value.class.include?(::Crystalline::MetadataFields)
133
+ value.fields.each do |field|
134
+ format_metadata = field.metadata.fetch(:format_json, {})
135
+ lookup = format_metadata.fetch(:letter_case, nil)&.call
136
+ field_val = value.send(field.name)
137
+
138
+ if raw_data.is_a?(::Hash) && lookup && raw_data.key?(lookup)
139
+ if field_val.class.include?(::Crystalline::MetadataFields) && raw_data[lookup].is_a?(::Hash)
140
+ nested_matched, nested_inexact, nested_unmatched = count_matched_fields(field_val, raw_data[lookup])
141
+ matched += nested_matched
142
+ inexact += nested_inexact
143
+ unmatched += nested_unmatched
144
+ else
145
+ matched += 1
146
+ if field_val.respond_to?(:known?) && !field_val.known?
147
+ inexact += 1
148
+ end
149
+ end
150
+ elsif !::Crystalline::Utils.nilable?(field.type)
151
+ unmatched += 1
152
+ end
153
+ end
154
+ elsif value.is_a?(::Array)
155
+ matched = value.length
156
+ elsif value.is_a?(::Hash)
157
+ matched = value.length
158
+ else
159
+ matched = 1
160
+ end
161
+
162
+ [matched, inexact, unmatched]
163
+ end
164
+
165
+ def self.non_nilable_attr_count(klass)
166
+ # somewhat sane sort ordering for Union deserialization.
167
+ # All Crystalline objects return the number of non-nilable fields
168
+ # All non-string primitives return 2
169
+ # All arrays and hashes return 1
170
+ # Strings return 0 (since any data can deserialize to a string, it should be our last attempt)
171
+ if klass.respond_to?(:fields)
172
+ return -1 *
173
+ klass.fields.count do |field|
174
+ !::Crystalline::Utils.nilable?(field.type)
175
+ end
176
+ else
177
+ if klass == String
178
+ return 0
179
+ elsif klass.is_a?(T::Types::TypedArray) || klass.is_a?(T::Types::TypedHash)
180
+ return 1
181
+ end
182
+
183
+ return 2
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,168 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Crystalline
5
+ class Array
6
+ attr_accessor :inner_type
7
+
8
+ def initialize(inner_type)
9
+ @inner_type = inner_type
10
+ end
11
+ end
12
+
13
+ class Hash
14
+ attr_accessor :key_type, :value_type
15
+
16
+ def initialize(key_type, value_type)
17
+ @key_type = key_type
18
+ @value_type = value_type
19
+ end
20
+ end
21
+
22
+ class Union
23
+ attr_accessor :types
24
+
25
+ def initialize(*types)
26
+ @types = types
27
+ end
28
+ end
29
+
30
+ class Nilable
31
+ attr_accessor :inner_type
32
+
33
+ def initialize(inner_type)
34
+ @inner_type = inner_type
35
+ end
36
+ end
37
+
38
+ class Boolean
39
+ end
40
+
41
+ # Wraps an unrecognized payload from an open discriminated union.
42
+ # Produced when the discriminator value is missing, unknown, or schema validation fails.
43
+ class Unknown
44
+ attr_reader :raw
45
+
46
+ def initialize(raw:)
47
+ @raw = raw
48
+ end
49
+
50
+ def unknown?
51
+ true
52
+ end
53
+ end
54
+
55
+ # Forward-compatible discriminated union parser.
56
+ # Known discriminator values are deserialized to their mapped types.
57
+ # Unknown or invalid payloads are captured as Crystalline::Unknown.
58
+ class DiscriminatedUnion
59
+ attr_reader :discriminator, :variants
60
+
61
+ def initialize(discriminator, variants)
62
+ @discriminator = discriminator
63
+ @variants = variants
64
+ end
65
+
66
+ def parse(payload)
67
+ unless payload.is_a?(::Hash)
68
+ return Unknown.new(raw: payload)
69
+ end
70
+
71
+ disc_value = payload[@discriminator]
72
+ unless disc_value.is_a?(::String)
73
+ return Unknown.new(raw: payload)
74
+ end
75
+
76
+ variant_type = @variants[disc_value]
77
+ unless variant_type
78
+ return Unknown.new(raw: payload)
79
+ end
80
+
81
+ begin
82
+ Crystalline.unmarshal_json(payload, variant_type)
83
+ rescue StandardError
84
+ Unknown.new(raw: payload)
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.unknown?(value)
90
+ value.is_a?(Unknown)
91
+ end
92
+
93
+ module Enum
94
+ def self.included(base)
95
+ base.extend(ClassMethods)
96
+ end
97
+
98
+ def initialize(val)
99
+ if self.class.instance_methods(false).include?(:initialize)
100
+ super(val)
101
+ else
102
+ @val = val
103
+ end
104
+ end
105
+
106
+ module ClassMethods
107
+ def enums(&blk)
108
+ @mapping = {}
109
+
110
+ yield
111
+ constants(false).each do |const_name|
112
+ instance = const_get(const_name, false)
113
+ unless instance.is_a? self
114
+ raise 'Enum constants must be instances of the Enum class (e.g. `Foo = new`)'
115
+ end
116
+ @mapping[instance.serialize] = instance
117
+ end
118
+ end
119
+
120
+ def open!
121
+ @open = true
122
+ end
123
+
124
+ def open?
125
+ @open == true
126
+ end
127
+
128
+ def deserialize(val)
129
+ if @mapping.include? val
130
+ @mapping[val]
131
+ elsif open?
132
+ new(val)
133
+ else
134
+ raise "Invalid value for enum: #{val}"
135
+ end
136
+ end
137
+ end
138
+
139
+ def serialize
140
+ @val
141
+ end
142
+
143
+ def to_s
144
+ serialize.to_s
145
+ end
146
+
147
+ def inspect
148
+ "#<#{self.class} #{serialize.inspect}>"
149
+ end
150
+
151
+ def known?
152
+ self.class.instance_variable_get(:@mapping)&.value?(self) || false
153
+ end
154
+
155
+ def ==(other)
156
+ other = other.serialize if other.is_a?(self.class)
157
+ @val == other
158
+ end
159
+
160
+ def eql?(other)
161
+ self == other
162
+ end
163
+
164
+ def hash
165
+ @val.hash
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,67 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Crystalline
5
+ module Utils
6
+ def self.arr?(t)
7
+ if t.instance_of? Crystalline::Array
8
+ return true
9
+ end
10
+ false
11
+ end
12
+
13
+ def self.arr_of(t)
14
+ t.inner_type
15
+ end
16
+
17
+ def self.hash?(t)
18
+ if t.instance_of? Crystalline::Hash
19
+ return true
20
+ end
21
+ false
22
+ end
23
+
24
+ def self.hash_of(t)
25
+ t.value_type
26
+ end
27
+
28
+ def self.nilable?(t)
29
+ if t.instance_of? Crystalline::Nilable
30
+ return true
31
+ end
32
+ if t.instance_of? Crystalline::Union
33
+ return t.types.any? { |tt| tt.instance_of? Crystalline::Nilable }
34
+ end
35
+ false
36
+ end
37
+
38
+ def self.nilable_of(t)
39
+
40
+ if nilable? t
41
+ return t.inner_type
42
+ end
43
+ nil
44
+ end
45
+
46
+ def self.union?(t)
47
+ return t.instance_of? Crystalline::Union
48
+ end
49
+
50
+ def self.get_union_types(t)
51
+ t.types
52
+ end
53
+
54
+ def self.boolean?(t)
55
+ t.instance_of? Crystalline::Boolean
56
+ end
57
+
58
+ def self.to_boolean(value)
59
+ case value
60
+ when true, false
61
+ value
62
+ else
63
+ raise TypeError, "Cannot convert #{value.inspect} to boolean. The value is of type #{value.class}"
64
+ end
65
+ end
66
+ end
67
+ end