goupnp/v2alpha/description/scpd/scpd.go
2022-05-25 18:27:18 +01:00

168 lines
4.4 KiB
Go

// Package scpd contains data structures that represent an SCPD at a higher level than XML.
package scpd
import (
"errors"
"fmt"
"sort"
"github.com/huin/goupnp/v2alpha/description/xmlscpd"
)
var (
BadDescriptionError = errors.New("bad XML description")
UnsupportedDescriptionError = errors.New("unsupported XML description")
)
// SCPD is the top level service description.
type SCPD struct {
actionByName map[string]*Action
variableByName map[string]*StateVariable
}
// FromXML creates an SCPD from XML data.
//
// It assumes that xmlDesc.Clean() has been called.
func FromXML(xmlDesc *xmlscpd.SCPD) (*SCPD, error) {
stateVariables := make(map[string]*StateVariable, len(xmlDesc.StateVariables))
for _, xmlSV := range xmlDesc.StateVariables {
sv, err := stateVariableFromXML(xmlSV)
if err != nil {
return nil, fmt.Errorf("processing state variable %q: %w", xmlSV.Name, err)
}
if _, exists := stateVariables[sv.name]; exists {
return nil, fmt.Errorf("%w: multiple state variables with name %q",
BadDescriptionError, sv.name)
}
stateVariables[sv.name] = sv
}
actions := make(map[string]*Action, len(xmlDesc.Actions))
for _, xmlAction := range xmlDesc.Actions {
action, err := actionFromXML(xmlAction)
if err != nil {
return nil, fmt.Errorf("processing action %q: %w", xmlAction.Name, err)
}
if _, exists := actions[action.name]; exists {
return nil, fmt.Errorf("%w: multiple actions with name %q",
BadDescriptionError, action.name)
}
actions[action.name] = action
}
return &SCPD{
actionByName: actions,
variableByName: stateVariables,
}, nil
}
// ActionNames returns the ordered names of each action.
func (scpd *SCPD) ActionNames() []string {
names := make([]string, 0, len(scpd.actionByName))
for name := range scpd.actionByName {
names = append(names, name)
}
sort.Strings(names)
return names
}
// Action returns an action with the given name.
func (scpd *SCPD) Action(name string) *Action {
return scpd.actionByName[name]
}
// Variable returns a state variable with the given name.
func (scpd *SCPD) Variable(name string) *StateVariable {
return scpd.variableByName[name]
}
// Action describes a single UPnP SOAP action.
type Action struct {
name string
inArgs []*Argument
outArgs []*Argument
}
// actionFromXML creates an Action from the given XML description.
func actionFromXML(xmlAction *xmlscpd.Action) (*Action, error) {
if xmlAction.Name == "" {
return nil, fmt.Errorf("%w: empty action name", BadDescriptionError)
}
var inArgs []*Argument
var outArgs []*Argument
for _, xmlArg := range xmlAction.Arguments {
arg, err := argumentFromXML(xmlArg)
if err != nil {
return nil, fmt.Errorf("processing argument %q: %w", xmlArg.Name, err)
}
switch xmlArg.Direction {
case "in":
inArgs = append(inArgs, arg)
case "out":
outArgs = append(outArgs, arg)
default:
return nil, fmt.Errorf("%w: argument %q has invalid direction %q",
BadDescriptionError, xmlArg.Name, xmlArg.Direction)
}
}
return &Action{
name: xmlAction.Name,
inArgs: inArgs,
outArgs: outArgs,
}, nil
}
// Argument description data.
type Argument struct {
name string
relatedStateVariable string
}
// argumentFromXML creates an Argument from the XML description.
func argumentFromXML(xmlArg *xmlscpd.Argument) (*Argument, error) {
if xmlArg.Name == "" {
return nil, fmt.Errorf("%w: empty argument name", BadDescriptionError)
}
if xmlArg.RelatedStateVariable == "" {
return nil, fmt.Errorf("%w: empty related state variable", BadDescriptionError)
}
return &Argument{
name: xmlArg.Name,
relatedStateVariable: xmlArg.RelatedStateVariable,
}, nil
}
func (arg *Argument) Name() string {
return arg.name
}
func (arg *Argument) RelatedStateVariableName() string {
return arg.relatedStateVariable
}
// StateVariable description data.
type StateVariable struct {
name string
dataType string
}
func stateVariableFromXML(xmlSV *xmlscpd.StateVariable) (*StateVariable, error) {
if xmlSV.Name == "" {
return nil, fmt.Errorf("%w: empty state variable name", BadDescriptionError)
}
if xmlSV.DataType.Type != "" {
return nil, fmt.Errorf("%w: unsupported data type %q",
UnsupportedDescriptionError, xmlSV.DataType.Type)
}
return &StateVariable{
name: xmlSV.Name,
dataType: xmlSV.DataType.Name,
}, nil
}
func (sv *StateVariable) Name() string {
return sv.name
}
func (sv *StateVariable) DataTypeName() string {
return sv.dataType
}