require "spec_helper" require "lutaml/model" module MultipleMapping class Product < Lutaml::Model::Serializable attribute :name, Lutaml::Model::Type::String attribute :localized_name, Lutaml::Model::Type::String attribute :description, Lutaml::Model::Type::String attribute :status, Lutaml::Model::Type::String attribute :content, Lutaml::Model::Type::String yaml do map ["name", "product_name"], to: :name map ["desc", "description"], to: :description end json do map ["name", "product_name"], to: :name map ["desc", "description"], to: :description end toml do map ["name", "product_name"], to: :name map ["desc", "description"], to: :description end xml do root "product" map_element ["name", "product-name"], to: :name map_element ["localized-name", "localized_name"], to: :localized_name map_element ["desc", "description"], to: :description map_attribute ["status", "product-status"], to: :status map_content to: :content end end class CustomModel < Lutaml::Model::Serializable attribute :id, Lutaml::Model::Type::String attribute :full_name, Lutaml::Model::Type::String attribute :size, Lutaml::Model::Type::Integer attribute :color, Lutaml::Model::Type::String attribute :description, Lutaml::Model::Type::String json do map ["name", "custom_name"], with: { to: :name_to_json, from: :name_from_json } map ["color", "shade"], with: { to: :color_to_json, from: :color_from_json } map ["size", "dimension"], with: { to: :size_to_json, from: :size_from_json } map ["desc", "description"], with: { to: :desc_to_json, from: :desc_from_json } end xml do root "CustomModel" map_attribute ["id", "identifier"], with: { to: :id_to_xml, from: :id_from_xml } map_element ["name", "custom-name"], with: { to: :name_to_xml, from: :name_from_xml } map_element ["color", "shade"], with: { to: :color_to_xml, from: :color_from_xml } map_element ["size", "dimension"], with: { to: :size_to_xml, from: :size_from_xml } map_element ["desc", "description"], with: { to: :desc_to_xml, from: :desc_from_xml } end # Custom methods for JSON def name_to_json(model, doc) doc["name"] = "JSON Model: #{model.full_name}" end def name_from_json(model, value) model.full_name = value&.sub(/^JSON Model: /, "") end def color_to_json(model, doc) doc["color"] = model.color.upcase end def color_from_json(model, value) model.color = value&.downcase end def size_to_json(model, doc) doc["size"] = model.size + 10 end def size_from_json(model, value) model.size = value - 10 end def desc_to_json(model, doc) doc["desc"] = "JSON Description: #{model.description}" end def desc_from_json(model, value) model.description = value&.sub(/^JSON Description: /, "") end # Custom methods for XML def id_to_xml(model, parent, doc) doc.add_attribute(parent, "id", "XML-#{model.id}") end def id_from_xml(model, value) model.id = value&.sub(/^XML-/, "") end def name_to_xml(model, parent, doc) el = doc.create_element("name") doc.add_text(el, "XML Model: #{model.full_name}") doc.add_element(parent, el) end def name_from_xml(model, value) model.full_name = value.sub(/^XML Model: /, "") end def color_to_xml(model, parent, doc) el = doc.create_element("color") doc.add_text(el, model.color.upcase) doc.add_element(parent, el) end def color_from_xml(model, value) model.color = value.downcase end def size_to_xml(model, parent, doc) el = doc.create_element("size") doc.add_text(el, (model.size + 10).to_s) doc.add_element(parent, el) end def size_from_xml(model, value) model.size = (value.to_i || 0) - 10 end def desc_to_xml(model, parent, doc) el = doc.create_element("desc") doc.add_text(el, "XML Description: #{model.description}") doc.add_element(parent, el) end def desc_from_xml(model, value) model.description = value.sub(/^XML Description: /, "") end end end RSpec.describe MultipleMapping do context "with key-value formats" do context "with YAML format" do let(:yaml_with_name) { "product_name: Coffee Maker\ndescription: Premium coffee maker" } let(:yaml_with_desc) { "---\nname: Coffee Maker\ndesc: Premium coffee maker\n" } it "handles bidirectional conversion" do product1 = MultipleMapping::Product.from_yaml(yaml_with_name) product2 = MultipleMapping::Product.from_yaml(yaml_with_desc) # keys for name and description are :name and :desc respectively since # they are first element in their respective mapping array expected_yaml = "---\nname: Coffee Maker\ndesc: Premium coffee maker\n" expect(product1.to_yaml).to eq(expected_yaml) expect(product2.to_yaml).to eq(yaml_with_desc) end end context "with JSON format" do let(:json_with_name) { '{"product_name":"Coffee Maker","description":"Premium coffee maker"}' } let(:json_with_desc) { '{"name":"Coffee Maker","desc":"Premium coffee maker"}' } it "handles bidirectional conversion" do product1 = MultipleMapping::Product.from_json(json_with_name) product2 = MultipleMapping::Product.from_json(json_with_desc) # keys for name and description are :name and :desc respectively since # they are first element in their respective mapping array expected_json = '{"name":"Coffee Maker","desc":"Premium coffee maker"}' expect(product1.to_json).to eq(expected_json) expect(product2.to_json).to eq(json_with_desc) end end end context "with XML format" do shared_examples "xml adapter with multiple mappings" do |adapter_class| before do Lutaml::Model::Config.xml_adapter = adapter_class end around do |example| old_adapter = Lutaml::Model::Config.xml_adapter Lutaml::Model::Config.xml_adapter = adapter_class example.run ensure Lutaml::Model::Config.xml_adapter = old_adapter end let(:xml_with_attributes) do <<~XML Some content here Coffee Maker Premium coffee maker XML end let(:xml_with_alternate_attributes) do <<~XML Different content Coffee Maker Premium coffee maker XML end it "handles bidirectional conversion with attributes and content" do product1 = MultipleMapping::Product.from_xml(xml_with_attributes) product2 = MultipleMapping::Product.from_xml(xml_with_alternate_attributes) # Key for element name is :name since it is first element in mapping array and same for status attribute expected_xml_product1 = <<~XML Coffee Maker Premium coffee maker Some content here XML expected_xml_product2 = <<~XML Coffee Maker Premium coffee maker Different content XML expect(product1.name).to eq("Coffee Maker") expect(product1.status).to eq("active") expect(product2.status).to eq("in-stock") expect(product1.to_xml).to be_equivalent_to(expected_xml_product1) expect(product2.to_xml).to be_equivalent_to(expected_xml_product2) end end context "with Nokogiri adapter" do it_behaves_like "xml adapter with multiple mappings", Lutaml::Model::XmlAdapter::NokogiriAdapter end context "with Ox adapter" do it_behaves_like "xml adapter with multiple mappings", Lutaml::Model::XmlAdapter::OxAdapter end end context "with CustomModel" do context "with JSON format" do let(:json_with_alternate) { '{"custom_name":"JSON Model: Vase","shade":"BLUE","dimension":22,"description":"JSON Description: A beautiful ceramic vase"}' } let(:json_with_standard) { '{"name":"JSON Model: Vase","color":"BLUE","size":22,"desc":"JSON Description: A beautiful ceramic vase"}' } it "handles bidirectional conversion with custom methods" do model1 = MultipleMapping::CustomModel.from_json(json_with_alternate) model2 = MultipleMapping::CustomModel.from_json(json_with_standard) # keys are 'name', 'color', 'size', 'desc' respectively since # they are first element in their respective mapping array expected_json = '{"name":"JSON Model: Vase","color":"BLUE","size":22,"desc":"JSON Description: A beautiful ceramic vase"}' expect(model1.to_json).to eq(expected_json) expect(model2.to_json).to eq(expected_json) end end context "with XML format" do shared_examples "xml adapter with custom methods" do |_adapter_class| before do Lutaml::Model::Config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter end let(:xml_with_alternate) do <<~XML XML Model: Vase BLUE 22 XML Description: A beautiful ceramic vase XML end let(:xml_with_standard) do <<~XML XML Model: Vase BLUE 22 XML Description: A beautiful ceramic vase XML end it "handles bidirectional conversion with custom methods" do model1 = MultipleMapping::CustomModel.from_xml(xml_with_alternate) model2 = MultipleMapping::CustomModel.from_xml(xml_with_standard) # Element names are 'name', 'color', 'size', 'desc' respectively since # they are first element in their respective mapping array expected_xml = <<~XML XML Model: Vase BLUE 22 XML Description: A beautiful ceramic vase XML expect(model1.to_xml).to be_equivalent_to(expected_xml) expect(model2.to_xml).to be_equivalent_to(expected_xml) end end context "with Nokogiri adapter" do it_behaves_like "xml adapter with custom methods", Lutaml::Model::XmlAdapter::NokogiriAdapter end context "with Ox adapter" do it_behaves_like "xml adapter with custom methods", Lutaml::Model::XmlAdapter::OxAdapter end end end end