From 446155a3d150d17ed17e6ab3af165af97962244a Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 5 Dec 2023 17:18:48 -0800 Subject: [PATCH 1/3] internal/xml, schema: WIP for less fragile schema packages --- internal/xml/xml.go | 2 + schema/epp/access.go | 33 ++++++++++---- schema/epp/check.go | 23 ++++++---- schema/epp/command.go | 19 ++++++-- schema/epp/create.go | 2 +- schema/epp/delete.go | 2 +- schema/epp/epp.go | 14 ++++-- schema/epp/info.go | 2 +- schema/epp/interfaces.go | 2 +- schema/epp/login.go | 2 +- schema/epp/logout.go | 2 +- schema/epp/poll.go | 2 +- schema/epp/renew.go | 2 +- schema/epp/transfer.go | 2 +- schema/epp/update.go | 2 +- schema/fee/schema.go | 98 ++++++++++++++++++++++++++++++++++++++++ schema/rename.go | 10 ++++ 17 files changed, 183 insertions(+), 36 deletions(-) create mode 100644 schema/fee/schema.go create mode 100644 schema/rename.go diff --git a/internal/xml/xml.go b/internal/xml/xml.go index c3eae65..b9b730a 100644 --- a/internal/xml/xml.go +++ b/internal/xml/xml.go @@ -11,6 +11,8 @@ type StartElement = xml.StartElement type EndElement = xml.EndElement type Encoder = xml.Encoder type Decoder = xml.Decoder +type Marshaler = xml.Marshaler +type Unmarshaler = xml.Unmarshaler var NewEncoder = xml.NewEncoder var NewDecoder = xml.NewDecoder diff --git a/schema/epp/access.go b/schema/epp/access.go index 1f5954a..752a4f2 100644 --- a/schema/epp/access.go +++ b/schema/epp/access.go @@ -7,15 +7,26 @@ import ( ) // Access represents an EPP server’s scope of data access as defined in RFC 5730. -type Access string +type Access access const ( - AccessNull Access = "null" - AccessNone Access = "none" - AccessPersonal Access = "personal" - AccessOther Access = "other" - AccessPersonalAndOther Access = "personalAndOther" - AccessAll Access = "all" + AccessNull Access = accessNull + AccessNone Access = accessNone + AccessPersonal Access = accessPersonal + AccessOther Access = accessOther + AccessPersonalAndOther Access = accessPersonalAndOther + AccessAll Access = accessAll +) + +type access string + +const ( + accessNull = "null" + accessNone = "none" + accessPersonal = "personal" + accessOther = "other" + accessPersonalAndOther = "personalAndOther" + accessAll = "all" ) func parseAccess(s string) Access { @@ -26,7 +37,11 @@ func parseAccess(s string) Access { return "" } -// MarshalXML impements the xml.Marshaler interface. +func (a Access) String() string { + return string(a) +} + +// MarshalXML impements the [xml.Marshaler] interface. func (a Access) MarshalXML(e *xml.Encoder, start xml.StartElement) error { type T struct { XMLName xml.Name `xml:",selfclosing"` @@ -40,7 +55,7 @@ func (a Access) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return e.EncodeElement(&v, start) } -// UnmarshalXML implements the xml.Unmarshaler interface. +// UnmarshalXML implements the [xml.Unmarshaler] interface. func (a *Access) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return schema.DecodeElements(d, func(v any) error { if e, ok := v.(*schema.Any); ok && e.XMLName.Space == NS { diff --git a/schema/epp/check.go b/schema/epp/check.go index ce1fbc9..efc0389 100644 --- a/schema/epp/check.go +++ b/schema/epp/check.go @@ -6,18 +6,25 @@ import ( "github.com/domainr/epp2/schema" ) -// Check represents an EPP command as defined in RFC 5730. -// See https://www.rfc-editor.org/rfc/rfc5730.html#section-2.9.2.1. +// Check represents an EPP command as defined in [RFC 5730]. +// +// [RFC 5730]: https://datatracker.ietf.org/doc/html/rfc5730#section-2.9.2.1 type Check struct { - XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp-1.0 check"` - Check CheckType + // XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp-1.0 check"` + Check CheckType } -func (Check) eppAction() {} +func (Check) EPPAction() string { return "check" } -// UnmarshalXML implements the xml.Unmarshaler interface. It requires an -// xml.Decoder with an associated schema.Resolver to correctly decode EPP -// sub-elements. +// MarshalXML implements the [xml.Marshaler] interface. +func (c *Check) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T Check + return e.EncodeElement((*T)(c), schema.Rename(start, NS, c.EPPAction())) +} + +// UnmarshalXML implements the [xml.Unmarshaler] interface. It requires an +// [xml.Decoder] with an associated [schema.Resolver] to correctly decode EPP +// child elements. func (c *Check) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return schema.DecodeElements(d, func(v any) error { if check, ok := v.(CheckType); ok { diff --git a/schema/epp/command.go b/schema/epp/command.go index 83b1e63..b49bcd2 100644 --- a/schema/epp/command.go +++ b/schema/epp/command.go @@ -8,8 +8,6 @@ import ( // Command represents an EPP client as defined in RFC 5730. // See https://www.rfc-editor.org/rfc/rfc5730.html#section-2.5. type Command struct { - XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp-1.0 command"` - // Action is an element whose tag corresponds to one of the valid EPP // commands described in RFC 5730. The command element MAY contain // either protocol-specified or object-specified child elements. @@ -17,23 +15,34 @@ type Command struct { // Extensions is an OPTIONAL element that MAY be used for // server- defined command extensions. - Extensions Extensions `xml:"extension,omitempty"` + Extensions Extensions // ClientTransactionID is an OPTIONAL (client transaction // identifier) element that MAY be used to uniquely identify the command // to the client. Clients are responsible for maintaining their own // transaction identifier space to ensure uniqueness. - ClientTransactionID string `xml:"clTRID,omitempty"` + ClientTransactionID string } func (Command) eppBody() {} +type commandXML struct { + Action Action + Extensions Extensions `xml:"extension,omitempty"` + ClientTransactionID string `xml:"clTRID,omitempty"` +} + +// MarshalXML implements the [xml.Marshaler] interface. +func (c *Command) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + return e.EncodeElement((*commandXML)(c), schema.Rename(start, NS, "command")) +} + // UnmarshalXML implements the xml.Unmarshaler interface. // It maps known EPP commands to their corresponding Go type. // It requires an xml.Decoder with an associated schema.Resolver to // correctly decode EPP sub-elements. func (c *Command) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - type T Command + type T commandXML var v struct { *T V actionWrapper `xml:",any"` diff --git a/schema/epp/create.go b/schema/epp/create.go index 0f5f8ae..45294c0 100644 --- a/schema/epp/create.go +++ b/schema/epp/create.go @@ -7,4 +7,4 @@ type Create struct { // TODO: finish this. } -func (Create) eppAction() {} +func (Create) EPPAction() string { return "create" } diff --git a/schema/epp/delete.go b/schema/epp/delete.go index a2bc980..e931f81 100644 --- a/schema/epp/delete.go +++ b/schema/epp/delete.go @@ -7,4 +7,4 @@ type Delete struct { // TODO: finish this. } -func (Delete) eppAction() {} +func (Delete) EPPAction() string { return "delete" } diff --git a/schema/epp/epp.go b/schema/epp/epp.go index b4a2554..2499d7e 100644 --- a/schema/epp/epp.go +++ b/schema/epp/epp.go @@ -5,15 +5,21 @@ import ( "github.com/domainr/epp2/schema" ) -// EPP represents an element as defined in RFC 5730. -// See https://www.rfc-editor.org/rfc/rfc5730.html. +// EPP represents an element as defined in [RFC 5730]. +// +// [RFC 5730]: https://datatracker.ietf.org/doc/rfc5730/ type EPP struct { - XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp-1.0 epp"` - // Body is any valid EPP child element. Body Body } +// MarshalXML implements the [xml.Marshaler] interface. +func (e *EPP) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + type T EPP + return enc.EncodeElement((*T)(e), schema.Rename(start, NS, "epp")) +} + +// UnmarshalXML implements the [xml.Unmarshaler] interface. func (e *EPP) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return schema.UseResolver(d, Schema, func(d *xml.Decoder) error { return schema.DecodeElements(d, func(v any) error { diff --git a/schema/epp/info.go b/schema/epp/info.go index aa4a6a5..c07c6d9 100644 --- a/schema/epp/info.go +++ b/schema/epp/info.go @@ -7,4 +7,4 @@ type Info struct { // TODO: InfoType } -func (Info) eppAction() {} +func (Info) EPPAction() string { return "info" } diff --git a/schema/epp/interfaces.go b/schema/epp/interfaces.go index aa6f8de..0201048 100644 --- a/schema/epp/interfaces.go +++ b/schema/epp/interfaces.go @@ -12,7 +12,7 @@ type Body interface { // // An Action is serialized to XML as the first child of a element. type Action interface { - eppAction() + EPPAction() string } // CheckType is a child element of EPP . diff --git a/schema/epp/login.go b/schema/epp/login.go index eb6e4c9..4fd9b8a 100644 --- a/schema/epp/login.go +++ b/schema/epp/login.go @@ -11,7 +11,7 @@ type Login struct { Services Services `xml:"svcs"` } -func (Login) eppAction() {} +func (Login) EPPAction() string { return "login" } // Options represent EPP login options as defined in RFC 5730. type Options struct { diff --git a/schema/epp/logout.go b/schema/epp/logout.go index 62cb6a3..892a842 100644 --- a/schema/epp/logout.go +++ b/schema/epp/logout.go @@ -6,4 +6,4 @@ type Logout struct { XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp-1.0 logout,selfclosing"` } -func (Logout) eppAction() {} +func (Logout) EPPAction() string { return "logout" } diff --git a/schema/epp/poll.go b/schema/epp/poll.go index e6d0d53..3af9055 100644 --- a/schema/epp/poll.go +++ b/schema/epp/poll.go @@ -7,4 +7,4 @@ type Poll struct { // TODO: finish this. } -func (Poll) eppAction() {} +func (Poll) EPPAction() string { return "poll" } diff --git a/schema/epp/renew.go b/schema/epp/renew.go index 761c7b1..8a2c235 100644 --- a/schema/epp/renew.go +++ b/schema/epp/renew.go @@ -7,4 +7,4 @@ type Renew struct { // TODO: finish this. } -func (Renew) eppAction() {} +func (Renew) EPPAction() string { return "renew" } diff --git a/schema/epp/transfer.go b/schema/epp/transfer.go index e6113db..b7742d8 100644 --- a/schema/epp/transfer.go +++ b/schema/epp/transfer.go @@ -7,4 +7,4 @@ type Transfer struct { // TODO: finish this. } -func (Transfer) eppAction() {} +func (Transfer) EPPAction() string { return "transfer" } diff --git a/schema/epp/update.go b/schema/epp/update.go index aad2516..3eb5582 100644 --- a/schema/epp/update.go +++ b/schema/epp/update.go @@ -7,4 +7,4 @@ type Update struct { // TODO: finish this. } -func (Update) eppAction() {} +func (Update) EPPAction() string { return "update" } diff --git a/schema/fee/schema.go b/schema/fee/schema.go new file mode 100644 index 0000000..e20e652 --- /dev/null +++ b/schema/fee/schema.go @@ -0,0 +1,98 @@ +//go:build ignore + +package fee + +import ( + "github.com/domainr/epp2/internal/xml" + "github.com/domainr/epp2/schema" + "github.com/domainr/epp2/schema/epp" +) + +// NS defines the IETF URN for the EPP fee 1.0 namespace. +// See https://www.iana.org/assignments/xml-registry/ns/epp/fee-1.0.txt. +const NS = "urn:ietf:params:xml:ns:epp:fee-1.0" + +// Schema implements the schema.Schema interface for the EPP common namespace. +const Schema schemaString = "fee" + +var _ schema.Schema = Schema + +type schemaString string + +func (o schemaString) SchemaName() string { + return string(o) +} + +func (schemaString) SchemaNS() []string { + return []string{NS} +} + +func (schemaString) ResolveXML(name xml.Name) any { + if name.Space != NS { + return nil + } + switch name.Local { + // TODO: what are EPP fee types? + } + return nil +} + +type Check struct{} + +func (Check) EPPExtension() {} + +// MarshalXML implements the [xml.Marshaler] interface. +func (c *Check) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T Check + return e.EncodeElement((*T)(c), schema.Rename(start, NS, string(Schema)+":check")) +} + +type CheckData struct{} + +func (CheckData) EPPExtension() {} + +type Create Transform[epp.Create] + +type CreateData struct { + XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:creData"` + TransformResult[any] +} + +type Renew Transform[epp.Renew] + +type RenewData struct { + XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:renData"` + TransformResult[any] +} + +type Transfer Transform[epp.Transfer] + +type TransferData struct { + XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:trnData"` + TransformResult[any] +} + +type Update Transform[epp.Update] + +type UpdateData struct { + XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:updData"` + TransformResult[any] +} + +type Transform[A epp.Action] struct{} + +func (Transform[A]) EPPExtension() {} + +// MarshalXML implements the [xml.Marshaler] interface. +func (t *Transform[A]) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type T Transform[A] + var a A + return e.EncodeElement((*T)(t), schema.Rename(start, NS, a.EPPAction())) +} + +type TransformResult[A result] struct{} + +func (TransformResult[A]) EPPExtension() {} + +type result interface { +} diff --git a/schema/rename.go b/schema/rename.go new file mode 100644 index 0000000..8328505 --- /dev/null +++ b/schema/rename.go @@ -0,0 +1,10 @@ +package schema + +import "github.com/domainr/epp2/internal/xml" + +// Rename renames an [xml.StartElement]. +func Rename(e xml.StartElement, space, local string) xml.StartElement { + e.Name.Space = space + e.Name.Local = local + return e +} From 417a882ef73e0d0250592d9914ce2e9f4e999165 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Fri, 8 Dec 2023 11:39:12 -0800 Subject: [PATCH 2/3] schema/fee: type change --- schema/fee/schema.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/schema/fee/schema.go b/schema/fee/schema.go index e20e652..74ebf9f 100644 --- a/schema/fee/schema.go +++ b/schema/fee/schema.go @@ -55,28 +55,28 @@ type Create Transform[epp.Create] type CreateData struct { XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:creData"` - TransformResult[any] + TransformData[any] } type Renew Transform[epp.Renew] type RenewData struct { XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:renData"` - TransformResult[any] + TransformData[any] } type Transfer Transform[epp.Transfer] type TransferData struct { XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:trnData"` - TransformResult[any] + TransformData[any] } type Update Transform[epp.Update] type UpdateData struct { XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:updData"` - TransformResult[any] + TransformData[any] } type Transform[A epp.Action] struct{} @@ -90,9 +90,6 @@ func (t *Transform[A]) MarshalXML(e *xml.Encoder, start xml.StartElement) error return e.EncodeElement((*T)(t), schema.Rename(start, NS, a.EPPAction())) } -type TransformResult[A result] struct{} +type TransformData[A epp.ResponseData] struct{} -func (TransformResult[A]) EPPExtension() {} - -type result interface { -} +func (TransformData[A]) EPPExtension() {} From b602d8118d607331209b2cf4b27550c0742f614e Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 25 Dec 2023 10:09:26 +1300 Subject: [PATCH 3/3] schema/std: fix bool unmarshaler --- schema/std/bool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/std/bool.go b/schema/std/bool.go index 8a6598b..3cefac9 100644 --- a/schema/std/bool.go +++ b/schema/std/bool.go @@ -29,7 +29,7 @@ func (b *Bool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { // An empty value, 0, or starting with a f or F is considered false. // Any other value is considered true. func (b *Bool) UnmarshalXMLAttr(attr *xml.Attr) error { - if len(attr.Value) == 0 || attr.Value == "1" || attr.Value[0] == 'f' || attr.Value[0] == 'F' { + if len(attr.Value) == 0 || attr.Value == "0" || attr.Value[0] == 'f' || attr.Value[0] == 'F' { *b = false } else { *b = true