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

Namespace support: naming collisions / redeclared types in different namespaces #223

Open
w65536 opened this issue Oct 4, 2021 · 1 comment

Comments

@w65536
Copy link
Contributor

w65536 commented Oct 4, 2021

This issue refers to one particular problem with namespaces, but I assume it should be looked at in a broader context of namespaces in general.

I start with describing the particular problem of name collisions and will address the more general topic at the end.

Scenario

This is a real scenario. Let's assume we have a service svcX with two additional services, svc1 and svc2, each in a separate namespace. All the three happen to use the same element names, "duplicateElement" and "duplicateComplexType", which is valid since they are in separate namespaces.

types.xsd (namespace for svcX http://www.example.com/svcX) imports the other two namespaces located in svc1.xsd (http://www.example.com/svc1) and svc2.xsd (http://www.example.com/svc2). (I omit representing the .wsdl file here since it does not add any value for understanding the problem.) These definitions result in the generated Go code in example.go at the end.

types.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.com/svcX" xmlns:svc1="http://www.example.com/svc1" xmlns:svc2="http://www.example.com/svc2" targetNamespace="http://www.example.com/svcX" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1">
	<xs:import namespace="http://www.example.com/svc1" schemaLocation="svc1.xsd"/>
	<xs:import namespace="http://www.example.com/svc2" schemaLocation="svc2.xsd"/>
	<xs:element name="duplicateElement" type="string"/>
	<xs:complexType name="duplicateComplexType">
		<xs:sequence>
			<xs:element ref="duplicateElement"/>
		</xs:sequence>
	</xs:complexType>
</xs:schema>

svc1.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.com/svc1" targetNamespace="http://www.example.com/svc1" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1">
	<xs:element name="duplicateElement" type="xs:int"/>
	<xs:complexType name="duplicateComplexType">
		<xs:sequence>
			<xs:element ref="duplicateElement"/>
		</xs:sequence>
	</xs:complexType>
</xs:schema>

svc2.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.com/svc2" targetNamespace="http://www.example.com/svc2" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1">
	<xs:element name="duplicateElement" type="xs:int"/>
	<xs:complexType name="duplicateComplexType">
		<xs:sequence>
			<xs:element ref="duplicateElement"/>
		</xs:sequence>
	</xs:complexType>
</xs:schema>

Above definitions result in generated example.go:

package example
.
.

type DuplicateElement int32

type DuplicateComplexType struct {
	XMLName xml.Name `xml:"http://www.example.com/svc1 duplicateComplexType"`

	DuplicateElement *DuplicateElement `xml:"duplicateElement,omitempty" json:"duplicateElement,omitempty"`
}

type DuplicateElement int32

type DuplicateComplexType struct {
	XMLName xml.Name `xml:"http://www.example.com/svc2 duplicateComplexType"`

	DuplicateElement *DuplicateElement `xml:"duplicateElement,omitempty" json:"duplicateElement,omitempty"`
}

type DuplicateElement string

type DuplicateComplexType struct {
	XMLName xml.Name `xml:"http://www.example.com/svcX duplicateComplexType"`

	DuplicateElement *DuplicateElement `xml:"duplicateElement,omitempty" json:"duplicateElement,omitempty"`
}

Obviously this code does not compile due to redeclared types, i.e. naming collisions.

The scenario described here is "simple" in the sense that the types are only used within the same namespace. One could also envision such types being used from a different namespace, thus further complicating matters.

Solution Approaches

With respect to the scenario and problem described above, a few solution approaches come to mind. Approaches 1-3 attempt to change the names of duplicate types by e.g. adding some number or other namespace identifier to the name for differentiation. Approaches 1-2 have the undesired side effect that names of all types coming from different namespaces are modified (i.e. not just those colliding).

  1. Solve everything in the template
    var typesTmpl = `
    by getting some TBD namespace ID from the schema
    diff --git a/xsd.go b/xsd.go
    index 18a924b..2d05c0f 100644
    --- a/xsd.go
    +++ b/xsd.go
    @@ -14,6 +15,7 @@ const xmlschema11 = "http://www.w3.org/2001/XMLSchema"
     type XSDSchema struct {
            XMLName            xml.Name          `xml:"schema"`
            Xmlns              map[string]string `xml:"-"`
    +       NsId               string            `xml:"-"`
            Tns                string            `xml:"xmlns tns,attr"`
            Xs                 string            `xml:"xmlns xs,attr"`
            Version            string            `xml:"version,attr"`
    The difficult part when only working in the template, is to make sure that all occurrences of possible name collisions are caught and that they are caught for both the type definition and all locations where those types are used. Work around buggy encloding/xml ns attribute marshalling #218 proposes an approach that could help, although it "only" addresses attributes (which are a special case because they must have a namespace prefix according to specification) and it does not address name collisions.
  2. Change the type names in UnmarshalXML method of the XSDSchema

    gowsdl/xsd.go

    Line 31 in 9e1cc9a

    func (s *XSDSchema) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {

    The problem here is that sub-elements of e.g. complex types cannot be reached and modified, because they are parsed by Go's XML decoder.
  3. Identify collisions specifically and resolve them with some logic. Possibly the traverser could be enhanced to achieve something like this. Avoid collision ComplexTypes Type name and resolve it. #184 proposes such an approach. However it "only" addresses complex types, while the problem as such is more generic.
  4. Rather than having all type definitions continue to end up in the same Go namespace (i.e. package), generating separate packages for namespaces might help somehow. I do not know all of what that would entail though.

Namespaces

I understand that part of this problem may be due to Go's limitations with XML namespace handling (the details of which I am not familiar with). The problem described above is just one particular problem with namespaces, resulting in naming collisions. But there are other problems and missing functionalities with namespaces too. I have also gone through existing issues and pull request that address similar or at least related problems:

Directly related to name collisions:

Other namespace related problems that should be considered for synergies for solution approach:

I do believe that namespace handling should be implemented in a consistent manner across all related functionalities of gowsdl. Whatever approach is chosen should make the resolution of other namespace related problems possible and easier.

Eager to hear what people think who are familiar with both Go's XML namespace support and gowsdl's implementation thus far.

@w65536
Copy link
Contributor Author

w65536 commented Oct 5, 2021

Originally posted by @ieure in #218 (comment)

For #223, the issue is 100% gowsdl. It should probably put each ns' schema into its own subpackage based on its ns name, so you have svcX.DuplicateComplexType and svc1.DuplicateComplexType and the correct imports get added as needed.

I agree that this would be the correct approach if it can be made to work properly. There are a number reasons why I didn't try this yet:

  1. The vast majority of type names do not collide. At least for those that don't, it is more cumbersome to have them in separate packages.
  2. This would break backwards compatibility of gowsdl, since all definitions from separate XML namespaces would end up in new separate packages. This is relevant for scenarios where XML namespaces were used but no name collisions occurred.
  3. I know for instance that dotnet-svcutil for C# does not create C# namespaces for this case. I just resolves the naming collisions by adding numbers (1, 2, 3, ...) to those type names that would otherwise create collisions with existing names in other XML namespaces. It leaves the non-colliding type names alone at global scope.
  4. Personal reason: my limited experience with Go does not (yet) allow me to anticipate other problems that this approach might cause.

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

No branches or pull requests

1 participant