Skip to content

draft: XML serialization revisions #18

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/xml/xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 24 additions & 9 deletions schema/epp/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"`
Expand All @@ -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 {
Expand Down
23 changes: 15 additions & 8 deletions schema/epp/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@ import (
"github.com/domainr/epp2/schema"
)

// Check represents an EPP <check> command as defined in RFC 5730.
// See https://www.rfc-editor.org/rfc/rfc5730.html#section-2.9.2.1.
// Check represents an EPP <check> 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 <check>
// 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
// <check> 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 {
Expand Down
19 changes: 14 additions & 5 deletions schema/epp/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,41 @@ import (
// Command represents an EPP client <command> 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.
Action Action

// Extensions is an OPTIONAL <extension> element that MAY be used for
// server- defined command extensions.
Extensions Extensions `xml:"extension,omitempty"`
Extensions Extensions

// ClientTransactionID is an OPTIONAL <clTRID> (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 <command> 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"`
Expand Down
2 changes: 1 addition & 1 deletion schema/epp/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ type Create struct {
// TODO: finish this.
}

func (Create) eppAction() {}
func (Create) EPPAction() string { return "create" }
2 changes: 1 addition & 1 deletion schema/epp/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ type Delete struct {
// TODO: finish this.
}

func (Delete) eppAction() {}
func (Delete) EPPAction() string { return "delete" }
14 changes: 10 additions & 4 deletions schema/epp/epp.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ import (
"github.com/domainr/epp2/schema"
)

// EPP represents an <epp> element as defined in RFC 5730.
// See https://www.rfc-editor.org/rfc/rfc5730.html.
// EPP represents an <epp> 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 {
Expand Down
2 changes: 1 addition & 1 deletion schema/epp/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ type Info struct {
// TODO: InfoType
}

func (Info) eppAction() {}
func (Info) EPPAction() string { return "info" }
2 changes: 1 addition & 1 deletion schema/epp/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Body interface {
//
// An Action is serialized to XML as the first child of a <command> element.
type Action interface {
eppAction()
EPPAction() string
}

// CheckType is a child element of EPP <check>.
Expand Down
2 changes: 1 addition & 1 deletion schema/epp/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion schema/epp/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
2 changes: 1 addition & 1 deletion schema/epp/poll.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ type Poll struct {
// TODO: finish this.
}

func (Poll) eppAction() {}
func (Poll) EPPAction() string { return "poll" }
2 changes: 1 addition & 1 deletion schema/epp/renew.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ type Renew struct {
// TODO: finish this.
}

func (Renew) eppAction() {}
func (Renew) EPPAction() string { return "renew" }
2 changes: 1 addition & 1 deletion schema/epp/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ type Transfer struct {
// TODO: finish this.
}

func (Transfer) eppAction() {}
func (Transfer) EPPAction() string { return "transfer" }
2 changes: 1 addition & 1 deletion schema/epp/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ type Update struct {
// TODO: finish this.
}

func (Update) eppAction() {}
func (Update) EPPAction() string { return "update" }
95 changes: 95 additions & 0 deletions schema/fee/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//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"`
TransformData[any]
}

type Renew Transform[epp.Renew]

type RenewData struct {
XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:renData"`
TransformData[any]
}

type Transfer Transform[epp.Transfer]

type TransferData struct {
XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:trnData"`
TransformData[any]
}

type Update Transform[epp.Update]

type UpdateData struct {
XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp:fee-1.0 fee:updData"`
TransformData[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 TransformData[A epp.ResponseData] struct{}

func (TransformData[A]) EPPExtension() {}
10 changes: 10 additions & 0 deletions schema/rename.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion schema/std/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down