Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3 ignore namespaces #5

Merged
merged 3 commits into from
Apr 21, 2024

Conversation

iyfedorov
Copy link

Hello! I didn't fully understand quasiquotes but can you look at it?

Copy link
Owner

@valentiay valentiay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! Thanks for your contribution!

I like the idea of modifying Cursor. It's good in its simplicity. The solution I was thinking of was much more complicated.

I've left some minor comments in the code, but there's also one major thing to consider. Look at this code sample:

import phobos.decoding.ElementDecoder
import phobos.decoding.XmlDecoder
import phobos.derivation.semiauto.deriveXmlDecoderConfigured
import phobos.configured.ElementCodecConfig
import phobos.derivation.semiauto.deriveElementDecoder
import phobos.syntax.xmlns

case object com
implicit val comNamespace: Namespace[com.type] = Namespace.mkInstance("example.com")

final case class Bar(@xmlns(com) qux: String)
// Default config with ignoreNamespaces=false is used
implicit val barDecoder: ElementDecoder[Bar] = deriveElementDecoder

val fooConfig = ElementCodecConfig.default.withIgnoreNamespaces()
final case class Foo(baz: String, @xmlns(com) bar: Bar)
implicit val fooDecoder: XmlDecoder[Foo] = deriveXmlDecoderConfigured("foo", fooConfig)

val string =
  """<com:foo xmlns:com="example.com">
    |  <com:baz>test2</com:baz><!-- Namespace is reset here -->
    |  <com:bar><com:qux>test1</com:qux></com:bar> <!-- But namespace is not reset here -->
    |</com:foo>
    |""".stripMargin

println(fooDecoder.decode(string))
// Right(Foo(test2,Bar(test1)))

Here, "ignoreNamespaces" applies to "baz" (because it uses a primitive string decoder), but not to "bar" (because it uses derived decoder with default configuration). This does not cause any bugs, but it is definitely counter-intuitive and can also be inconvenient.

I think that "ignoreNamespaces" should work like "scopeDefaultNamespace", which affects all elements within the current one. If "ignoreNamespaces" is made an optional setting, then it will be possible to implement this behavior with a stack within "Cursor", just as for "scopeDefaultNamespace". This way the behaviour would not only be more intuitive but you would also not have to set "ignoreNamespace" for every derived decoder.

@@ -18,5 +19,43 @@ class ElementDecoderTest extends AnyWordSpec with Matchers {
case Right(value) => value.date shouldBe OffsetDateTime.parse("2019-10-27T18:27:26.1279855+05:00");
}
}

"ignore namespaces by configuration" in {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this test should be in DecoderDerivationTest.scala

@@ -55,6 +55,9 @@ import phobos.Namespace
* the tuple is URL, second - optional preferred prefix of the namespace. The namespaces are only declared, but are
* not assigned to any elements or attributes. These declarations may be used by nested elements. This setting helps
* to avoid duplicate namespace declarations. No namespaces are defined by default.
*
* @param ignoreNamespaces
* Affects only decoders. Ignore all namespaces in xml
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This setting needs a little more documentation. It actually does not ignore namespaces, but it makes all namespaces empty. Therefore, decoding may still fail if a namespace is specified for some inner elements.

Maybe it would be better to rename this to "removeNamespaces" or something similar.

@@ -144,6 +148,9 @@ final case class ElementCodecConfig(
/** See docs for [[scopeDefaultNamespace]]. */
def withScopeDefaultNamespace[NS](namespace: NS)(implicit ns: Namespace[NS]): ElementCodecConfig =
copy(scopeDefaultNamespace = Some(ns.getNamespace))

def withIgnoreNamespaces(isIgnoreNamespaces: Boolean = true): ElementCodecConfig =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be more convenient to remove this parameter from the method and change its implementation to copy(ignoreNamespaces=true), as false is the default option

@valentiay valentiay added this to the 0.22.0 milestone Apr 14, 2024
@iyfedorov
Copy link
Author

Hi! Thanks for your contribution!

I like the idea of modifying Cursor. It's good in its simplicity. The solution I was thinking of was much more complicated.

I've left some minor comments in the code, but there's also one major thing to consider. Look at this code sample:

import phobos.decoding.ElementDecoder
import phobos.decoding.XmlDecoder
import phobos.derivation.semiauto.deriveXmlDecoderConfigured
import phobos.configured.ElementCodecConfig
import phobos.derivation.semiauto.deriveElementDecoder
import phobos.syntax.xmlns

case object com
implicit val comNamespace: Namespace[com.type] = Namespace.mkInstance("example.com")

final case class Bar(@xmlns(com) qux: String)
// Default config with ignoreNamespaces=false is used
implicit val barDecoder: ElementDecoder[Bar] = deriveElementDecoder

val fooConfig = ElementCodecConfig.default.withIgnoreNamespaces()
final case class Foo(baz: String, @xmlns(com) bar: Bar)
implicit val fooDecoder: XmlDecoder[Foo] = deriveXmlDecoderConfigured("foo", fooConfig)

val string =
  """<com:foo xmlns:com="example.com">
    |  <com:baz>test2</com:baz><!-- Namespace is reset here -->
    |  <com:bar><com:qux>test1</com:qux></com:bar> <!-- But namespace is not reset here -->
    |</com:foo>
    |""".stripMargin

println(fooDecoder.decode(string))
// Right(Foo(test2,Bar(test1)))

Here, "ignoreNamespaces" applies to "baz" (because it uses a primitive string decoder), but not to "bar" (because it uses derived decoder with default configuration). This does not cause any bugs, but it is definitely counter-intuitive and can also be inconvenient.

I think that "ignoreNamespaces" should work like "scopeDefaultNamespace", which affects all elements within the current one. If "ignoreNamespaces" is made an optional setting, then it will be possible to implement this behavior with a stack within "Cursor", just as for "scopeDefaultNamespace". This way the behaviour would not only be more intuitive but you would also not have to set "ignoreNamespace" for every derived decoder.

It`s true. Propagation strategy will be more convenient. I will try to add this

Copy link
Owner

@valentiay valentiay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Now everything looks good to me. I will merge, when pipeline succeeds

@valentiay valentiay merged commit 4d5ee65 into valentiay:master Apr 21, 2024
1 check failed
@valentiay valentiay mentioned this pull request Apr 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants