package declaration import ( "encoding/json" "fmt" ) // ParseResource reads one resource as Parse reads each of a declaration's resources: by its type, strictly, refusing // a field its kind does not use and anything its kind refuses, and never an action (the link may not carry one, ADR // 0005). It judges the resource alone: what Parse judges across resources (an opening on a node not adopted, the // files the provisioner fills, what replaces what) is for the whole a node applies (novox/hq ADR 0539 clause 1: a // composition is one module's resources, and a node applies its compositions together with its own key). func ParseResource(raw json.RawMessage) (Resource, error) { var head struct { ID string `json:"id"` Type Type `json:"type"` } if err := json.Unmarshal(raw, &head); err != nil { return nil, &RefusalError{Problems: []string{"not a resource: " + err.Error()}} } where := "resource" if head.ID == "" { return nil, &RefusalError{Problems: []string{where + ": no id. Identity is what lets the host know this is " + "the same resource it applied last time"}} } where = fmt.Sprintf("resource %q", head.ID) resource := newOf(head.Type) if resource == nil { return nil, &RefusalError{Problems: []string{fmt.Sprintf("%s: unknown type %q. This host understands %s", where, head.Type, vocabulary())}} } if unknown := unknownFields(raw, resource); len(unknown) > 0 { var problems []string for _, field := range unknown { problems = append(problems, fmt.Sprintf("%s: a %s does not use %q, and it is set. Refused rather "+ "than ignored", where, head.Type, field)) } return nil, &RefusalError{Problems: problems} } if err := json.Unmarshal(raw, resource); err != nil { return nil, &RefusalError{Problems: []string{fmt.Sprintf("%s: %s", where, err)}} } if problems := resource.validate(where, false); len(problems) > 0 { return nil, &RefusalError{Problems: problems} } return resource, nil }