my_data 0.1.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +37 -0
  3. data/.rubocop.yml +12 -2
  4. data/Gemfile +9 -4
  5. data/Guardfile +7 -0
  6. data/README.md +55 -8
  7. data/lib/my_data.rb +9 -10
  8. data/lib/my_data/client.rb +34 -11
  9. data/lib/my_data/resource.rb +43 -29
  10. data/lib/my_data/resources/continuation_token_type.rb +7 -0
  11. data/lib/my_data/resources/ecls/expenses_classification.rb +7 -0
  12. data/lib/my_data/resources/ecls/expenses_classification_type.rb +1 -1
  13. data/lib/my_data/resources/ecls/invoice_expenses_classification_type.rb +1 -1
  14. data/lib/my_data/resources/ecls/invoices_expenses_classification_detail_type.rb +1 -1
  15. data/lib/my_data/resources/error_type.rb +1 -2
  16. data/lib/my_data/resources/icls/income_classification.rb +7 -0
  17. data/lib/my_data/resources/icls/income_classification_type.rb +1 -1
  18. data/lib/my_data/resources/icls/invoice_income_classification_type.rb +1 -1
  19. data/lib/my_data/resources/icls/invoices_income_classification_detail_type.rb +1 -1
  20. data/lib/my_data/resources/inv/aade_book_invoice_type.rb +1 -1
  21. data/lib/my_data/resources/inv/address_type.rb +1 -1
  22. data/lib/my_data/resources/inv/cancelled_invoice_type.rb +7 -0
  23. data/lib/my_data/resources/inv/continuation_token_type.rb +7 -0
  24. data/lib/my_data/resources/inv/invoice_header_type.rb +1 -1
  25. data/lib/my_data/resources/inv/invoice_row_type.rb +1 -1
  26. data/lib/my_data/resources/inv/invoice_summary_type.rb +1 -1
  27. data/lib/my_data/resources/inv/invoices_doc.rb +1 -11
  28. data/lib/my_data/resources/inv/party_type.rb +1 -1
  29. data/lib/my_data/resources/inv/payment_method_detail_type.rb +1 -1
  30. data/lib/my_data/resources/inv/request_doc.rb +7 -0
  31. data/lib/my_data/resources/inv/requested_invoices_doc.rb +7 -0
  32. data/lib/my_data/resources/inv/ship_type.rb +1 -1
  33. data/lib/my_data/resources/inv/tax_totals_type.rb +1 -1
  34. data/lib/my_data/resources/invoice_provider_type.rb +7 -0
  35. data/lib/my_data/resources/requested_provider_doc.rb +7 -0
  36. data/lib/my_data/resources/response.rb +7 -0
  37. data/lib/my_data/resources/response_type.rb +1 -13
  38. data/lib/my_data/response_parser.rb +42 -0
  39. data/lib/my_data/type_caster.rb +5 -4
  40. data/lib/my_data/version.rb +1 -1
  41. data/lib/my_data/xml_generator.rb +29 -17
  42. data/lib/my_data/xml_parser.rb +37 -76
  43. data/lib/my_data/xsd/complex_type.rb +11 -3
  44. data/lib/my_data/xsd/doc.rb +77 -0
  45. data/lib/my_data/xsd/element.rb +20 -8
  46. data/lib/my_data/xsd/resource_generator.rb +26 -13
  47. data/lib/my_data/xsd/structure.rb +41 -35
  48. data/my_data.gemspec +4 -3
  49. metadata +41 -19
  50. data/lib/my_data/resources.rb +0 -10
  51. data/lib/my_data/resources/ecls.rb +0 -7
  52. data/lib/my_data/resources/icls.rb +0 -7
  53. data/lib/my_data/resources/inv.rb +0 -14
  54. data/lib/my_data/resources/responses_doc.rb +0 -8
  55. data/lib/my_data/xsd.rb +0 -7
@@ -3,6 +3,5 @@
3
3
  class MyData::Resources::ErrorType
4
4
  include MyData::Resource
5
5
 
6
- attribute :message, :string
7
- attribute :code, :integer
6
+ xsd_complex_type
8
7
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::Icls::IncomeClassification
4
+ include MyData::Resource
5
+
6
+ xsd_doc
7
+ end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Icls::IncomeClassificationType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Icls::InvoiceIncomeClassificationType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Icls::InvoicesIncomeClassificationDetailType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::AadeBookInvoiceType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::AddressType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::Inv::CancelledInvoiceType
4
+ include MyData::Resource
5
+
6
+ xsd_complex_type
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::Inv::ContinuationTokenType
4
+ include MyData::Resource
5
+
6
+ xsd_complex_type
7
+ end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::InvoiceHeaderType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::InvoiceRowType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::InvoiceSummaryType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,15 +3,5 @@
3
3
  class MyData::Resources::Inv::InvoicesDoc
4
4
  include MyData::Resource
5
5
 
6
- container_tag "InvoicesDoc", {
7
- xmlns: "http://www.aade.gr/myDATA/invoice/v1.0",
8
- "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
9
- "xsi:schemaLocation": "http://www.aade.gr/myDATA/invoice/v1.0 schema.xsd",
10
- "xmlns:icls": "https://www.aade.gr/myDATA/incomeClassificaton/v1.0"
11
- }
12
-
13
- attribute :invoice, :resource,
14
- class_name: "Inv::AadeBookInvoiceType", collection: true
15
-
16
- validates_presence_of :invoice
6
+ xsd_doc
17
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::PartyType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::PaymentMethodDetailType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::Inv::RequestDoc
4
+ include MyData::Resource
5
+
6
+ xsd_doc
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::Inv::RequestedInvoicesDoc
4
+ include MyData::Resource
5
+
6
+ xsd_doc
7
+ end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::ShipType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -3,5 +3,5 @@
3
3
  class MyData::Resources::Inv::TaxTotalsType
4
4
  include MyData::Resource
5
5
 
6
- xsd_structure
6
+ xsd_complex_type
7
7
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::InvoiceProviderType
4
+ include MyData::Resource
5
+
6
+ xsd_complex_type
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::RequestedProviderDoc
4
+ include MyData::Resource
5
+
6
+ xsd_doc
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::Resources::Response
4
+ include MyData::Resource
5
+
6
+ xsd_doc
7
+ end
@@ -3,17 +3,5 @@
3
3
  class MyData::Resources::ResponseType
4
4
  include MyData::Resource
5
5
 
6
- attribute :index, :integer
7
-
8
- attribute :invoice_uid, :string
9
- attribute :invoice_mark, :integer
10
- attribute :classification_mark, :integer
11
- attribute :cancellation_mark, :integer
12
- attribute :authentication_code, :string
13
-
14
- attribute :errors, :resource,
15
- class_name: "ErrorType",
16
- collection: true, collection_element_name: "error"
17
-
18
- attribute :status_code, :string
6
+ xsd_complex_type
19
7
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MyData::ResponseParser
4
+ def initialize(response, resource: nil, root: nil)
5
+ @original_response = response
6
+ @resource = resource
7
+ @root = root
8
+ end
9
+
10
+ def success?
11
+ original_response.status == 200 &&
12
+ (!response_type? || response.response.all? { |r| r.status_code == "Success" })
13
+ end
14
+
15
+ def errors
16
+ @errors ||=
17
+ if success?
18
+ []
19
+ elsif response_type?
20
+ response.response.map(&:errors).flatten
21
+ else
22
+ [MyData::Resources::ErrorType.new(JSON.parse(original_response.body))]
23
+ end
24
+ end
25
+
26
+ def response
27
+ return @response if defined? @response
28
+
29
+ @response =
30
+ if original_response.status == 200
31
+ MyData::XmlParser.xml_to_resource(xml: original_response.body, resource: resource, root: root)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def response_type?
38
+ response&.is_a?(MyData::Resources::Response)
39
+ end
40
+
41
+ attr_reader :original_response, :resource, :root
42
+ end
@@ -11,8 +11,7 @@ module MyData
11
11
  :date,
12
12
  :time,
13
13
  :boolean,
14
- :resource,
15
- :ignore
14
+ :resource
16
15
  ].freeze
17
16
 
18
17
  def valid_type?(type)
@@ -28,7 +27,7 @@ module MyData
28
27
  private
29
28
 
30
29
  def type_cast_string(value)
31
- value.to_s.strip
30
+ value ? value.to_s.strip : nil
32
31
  end
33
32
 
34
33
  def type_cast_integer(value)
@@ -48,7 +47,9 @@ module MyData
48
47
  end
49
48
 
50
49
  def type_cast_boolean(value)
51
- value.downcase == "true"
50
+ return value if value.nil?
51
+
52
+ value.is_a?(String) ? value.downcase == "true" : value == true
52
53
  end
53
54
 
54
55
  def type_cast_resource(value, resource)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MyData
4
- VERSION = "0.1.0"
4
+ VERSION = "0.9.0"
5
5
  end
@@ -22,28 +22,40 @@ module MyData
22
22
 
23
23
  def to_xml_structure
24
24
  resource.attributes.each_with_object([]) do |(key, value), structure|
25
- attr_mappings = mappings[key]
26
- next if value.nil?
27
-
28
- if attr_mappings[:collection]
29
- val = value.map { |v| attr_mappings[:resource] ? self.class.new(v, parent_namespace: namespace).to_xml_structure : v.to_s }
30
-
31
- if attr_mappings[:collection_element_name]
32
- struct = val.map { |v| attr_structure(attr_mappings[:collection_element_name], v) }
33
- structure.push(attr_structure(key, struct))
34
- else
35
- struct = val.map { |v| attr_structure(key, v) }
36
- structure.push(*struct)
37
- end
38
- else
39
- val = attr_mappings[:resource] ? self.class.new(value, parent_namespace: namespace).to_xml_structure : value.to_s
40
- structure.push(attr_structure(key, val))
41
- end
25
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
26
+
27
+ value = value_to_xml_structure(key, value)
28
+
29
+ structure.push(*extract_attributes(key, value))
42
30
  end
43
31
  end
44
32
 
45
33
  private
46
34
 
35
+ def value_to_xml_structure(key, value)
36
+ is_resource = mappings[key][:resource].present?
37
+
38
+ transform = lambda do |v|
39
+ is_resource ? self.class.new(v, parent_namespace: namespace).to_xml_structure : v.to_s
40
+ end
41
+
42
+ value.is_a?(Array) ? value.map(&transform) : transform.call(value)
43
+ end
44
+
45
+ def extract_attributes(key, value)
46
+ collection, collection_element_name = mappings[key].values_at(:collection, :collection_element_name)
47
+
48
+ if !collection
49
+ [attr_structure(key, value)]
50
+ elsif collection_element_name
51
+ attrs = value.map { |v| attr_structure(collection_element_name, v) }
52
+
53
+ [attr_structure(key, attrs)]
54
+ else
55
+ value.map { |v| attr_structure(key, v) }
56
+ end
57
+ end
58
+
47
59
  def attr_structure(key, value)
48
60
  prefix = parent_namespace && namespace != parent_namespace ? "#{namespace}:" : ""
49
61
 
@@ -1,96 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module MyData
4
- class XmlParser
5
- attr_reader :doc, :mappings, :class_name
3
+ module MyData::XmlParser
4
+ extend self
6
5
 
7
- def initialize(doc, mappings, class_name)
8
- @doc = doc
9
- @mappings = mappings
10
- @class_name = class_name
11
- end
12
-
13
- def parse_xml
14
- doc.children.select(&:element?).each_with_object({}) do |element, parse_attrs|
15
- attr_names = extract_attr_names(element)
16
-
17
- attr_names.each do |attr_name|
18
- value = extract_value(element, attr_name)
19
- parse_attrs[attr_name] = value if value
20
- end
21
- end
22
- end
6
+ def xml_to_resource(xml:, resource:, root: nil)
7
+ h = transofrm_xml_to_hash(fix_xml(xml))
8
+ h = h[root] if root
23
9
 
24
- private
25
-
26
- def extract_attr_names(element)
27
- posible_attrs = mappings.select { |_, opts| element.name == opts[:tag_name] }
10
+ resource.new hash_mapping(h, resource)
11
+ end
28
12
 
29
- if posible_attrs.present?
30
- attr_names = posible_attrs
31
- .select { |_name, opts| match_static_tag_attributes?(element, opts[:static_tag_attrs]) }
32
- else
33
- attr_names = []
34
- # raise "Not implemented: #{class_name}: #{element.name}"
35
- end
13
+ private
36
14
 
37
- if attr_names.empty?
38
- # raise("Tag attributes not matched: #{class_name}: #{element.name}, element attrs: #{element.attributes}")
39
- end
15
+ def hash_mapping(hash, resource)
16
+ flatten(hash, resource).each_with_object({}) do |(key, value), h|
17
+ mappings = resource.mappings[key]
40
18
 
41
- attr_names.map(&:first)
19
+ h[key] = value_mapping(value, mappings)
42
20
  end
21
+ end
43
22
 
44
- def extract_value(element, attr_name)
45
- value = element
46
- attr_opts = mappings[attr_name]
47
-
48
- # TODO: current we dont check that the attribute setting matcing the elements
49
- if attr_opts[:nested_tags].present?
50
- attr_opts[:nested_tags].each do |tag|
51
- return if value.nil?
23
+ def value_mapping(value, mappings)
24
+ return value if mappings[:resource].nil?
52
25
 
53
- value = value.children.select(&:element?).find { |element| element.name == tag[:name].to_s }
54
- end
26
+ if mappings[:collection]
27
+ value = value.is_a?(Array) ? value : [value]
28
+ value.map { |v| hash_mapping(v, mappings[:resource]) }
29
+ else
30
+ hash_mapping(value, mappings[:resource])
31
+ end
32
+ end
55
33
 
56
- return value if value.nil?
57
- end
34
+ def fix_xml(xml)
35
+ xml.strip.gsub("&lt;", "<").gsub("&gt;", ">")
36
+ end
58
37
 
59
- if attr_opts[:collection]
60
- value.children.select(&:element?).map { |val| get_value(val, attr_opts) }
61
- else
62
- get_value(value, attr_opts)
63
- end
64
- end
38
+ def transofrm_xml_to_hash(xml)
39
+ Hash
40
+ .from_xml(xml)
41
+ .deep_transform_keys(&:underscore)["string"]
42
+ end
65
43
 
66
- def get_value(value, attr_opts)
67
- if attr_opts[:type] == :model
68
- nested_parser = self.class.new(
69
- value,
70
- attr_opts[:class_name].mappings,
71
- attr_opts[:class_name].name
72
- )
73
- value = nested_parser.parse_xml
74
- else
75
- value = if attr_opts[:dynamic_tag_attr].present?
76
- value.attributes[attr_opts[:dynamic_tag_attr]].value
77
- else
78
- value.text
79
- end
80
- end
81
- end
44
+ def flatten(hash, resource)
45
+ return {} unless hash
82
46
 
83
- def match_static_tag_attributes?(element, static_tag_attributes)
84
- static_tag_attributes.all? do |tag_attribute|
85
- element_attr = element.attributes[tag_attribute.name]
47
+ hash.each_with_object({}) do |(k, v), h|
48
+ next if resource.attributes.none?(k) || !v
86
49
 
87
- next false unless element_attr
50
+ mappings = resource.mappings[k]
88
51
 
89
- matched = (element_attr.value == tag_attribute.value) &&
90
- (tag_attribute.namespace.nil? || tag_attribute.namespace == element_attr.namespace.prefix)
52
+ next h[k] = v unless mappings[:collection] && mappings[:collection_element_name]
91
53
 
92
- matched
93
- end
54
+ h[k] = v[mappings[:collection_element_name]]
94
55
  end
95
56
  end
96
57
  end