package plc import ( "encoding/json" "fmt" "maps" "github.com/johnandersen777/socialweb-computer-kcp/internal/kit/strs" ) const ( TypeOperation = "plc_operation" TypeTombstone = "plc_tombstone" TypeCreate = "create" ) type Service struct { Type string `json:"type"` Endpoint string `json:"endpoint"` } type Operation struct { Type string Prev *string RotationKeys []string VerificationMethods map[string]string AlsoKnownAs []string Services map[string]Service SigningKey string RecoveryKey string Handle string Service string Sig string raw map[string]any } type LogEntry struct { DID string `json:"did"` Operation Operation `json:"operation"` CID string `json:"cid"` Nullified bool `json:"nullified"` CreatedAt string `json:"createdAt"` } type VerificationMethod struct { ID string `json:"id"` Type string `json:"type"` Controller string `json:"controller"` PublicKeyMultibase string `json:"publicKeyMultibase"` } type DIDService struct { ID string `json:"id"` Type string `json:"type"` ServiceEndpoint string `json:"serviceEndpoint"` } type DIDDocument struct { Context []string `json:"@context"` ID string `json:"id"` AlsoKnownAs []string `json:"alsoKnownAs,omitempty"` VerificationMethod []VerificationMethod `json:"verificationMethod,omitempty"` Service []DIDService `json:"service,omitempty"` } func (o Operation) MarshalJSON() ([]byte, error) { if o.raw != nil { return json.Marshal(o.raw) } return json.Marshal(o.object()) } func (o *Operation) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } o.fromObject(raw) o.raw = raw return nil } func NewOperationFromObject(raw map[string]any) *Operation { op := &Operation{} op.fromObject(raw) op.raw = raw return op } func (o *Operation) SignedObject() map[string]any { m := o.object() if o.Sig != "" { m["sig"] = o.Sig } return m } func (o *Operation) UnsignedObject() map[string]any { m := o.object() delete(m, "sig") return m } func (o *Operation) object() map[string]any { if o.raw != nil { out := make(map[string]any, len(o.raw)) maps.Copy(out, o.raw) return out } m := map[string]any{"type": o.Type} switch o.Type { case TypeOperation: m["rotationKeys"] = strs.AnySlice(o.RotationKeys) m["verificationMethods"] = strs.AnyMap(o.VerificationMethods) m["alsoKnownAs"] = strs.AnySlice(o.AlsoKnownAs) services := make(map[string]any, len(o.Services)) for k, v := range o.Services { services[k] = map[string]any{"type": v.Type, "endpoint": v.Endpoint} } m["services"] = services if o.Prev != nil { m["prev"] = *o.Prev } else { m["prev"] = nil } case TypeTombstone: if o.Prev != nil { m["prev"] = *o.Prev } else { m["prev"] = nil } case TypeCreate: m["signingKey"] = o.SigningKey m["recoveryKey"] = o.RecoveryKey m["handle"] = o.Handle m["service"] = o.Service } if o.Sig != "" { m["sig"] = o.Sig } return m } func (o *Operation) fromObject(raw map[string]any) { o.raw = nil o.Type, _ = raw["type"].(string) o.Sig, _ = raw["sig"].(string) o.Prev = nil if v, ok := raw["prev"]; ok && v != nil { if s, ok := v.(string); ok { o.Prev = &s } } o.RotationKeys = stringsFromAny(raw["rotationKeys"]) o.VerificationMethods = stringMapFromAny(raw["verificationMethods"]) o.AlsoKnownAs = stringsFromAny(raw["alsoKnownAs"]) o.Services = servicesFromAny(raw["services"]) o.SigningKey, _ = raw["signingKey"].(string) o.RecoveryKey, _ = raw["recoveryKey"].(string) o.Handle, _ = raw["handle"].(string) o.Service, _ = raw["service"].(string) } func stringsFromAny(v any) []string { arr, _ := v.([]any) out := make([]string, 0, len(arr)) for _, el := range arr { if s, ok := el.(string); ok { out = append(out, s) } } return out } func stringMapFromAny(v any) map[string]string { m, _ := v.(map[string]any) out := make(map[string]string, len(m)) for k, el := range m { if s, ok := el.(string); ok { out[k] = s } } return out } func servicesFromAny(v any) map[string]Service { m, _ := v.(map[string]any) out := make(map[string]Service, len(m)) for k, el := range m { svc, ok := el.(map[string]any) if !ok { continue } s := Service{} s.Type, _ = svc["type"].(string) s.Endpoint, _ = svc["endpoint"].(string) out[k] = s } return out } func ValidateStructure(raw map[string]any) string { if raw == nil { return "Operation must be an object" } typ, _ := raw["type"].(string) switch typ { case TypeOperation: if _, ok := raw["rotationKeys"].([]any); !ok { return "rotationKeys must be an array" } if v, ok := raw["verificationMethods"]; ok && v != nil { if _, ok := v.(map[string]any); !ok { return "verificationMethods must be an object" } } if v, ok := raw["alsoKnownAs"]; ok && v != nil { if _, ok := v.([]any); !ok { return "alsoKnownAs must be an array" } } if v, ok := raw["services"]; ok && v != nil { if _, ok := v.(map[string]any); !ok { return "services must be an object" } } if _, ok := raw["sig"].(string); !ok { return "sig must be a string" } if v, ok := raw["prev"]; ok && v != nil { if _, ok := v.(string); !ok { return "prev must be null or a CID string" } } return "" case TypeTombstone: if _, ok := raw["prev"].(string); !ok { return "prev must be a CID string" } if _, ok := raw["sig"].(string); !ok { return "sig must be a string" } return "" case TypeCreate: if _, ok := raw["signingKey"].(string); !ok { return "signingKey must be a string" } if _, ok := raw["recoveryKey"].(string); !ok { return "recoveryKey must be a string" } if _, ok := raw["handle"].(string); !ok { return "handle must be a string" } if _, ok := raw["service"].(string); !ok { return "service must be a string" } if _, ok := raw["sig"].(string); !ok { return "sig must be a string" } return "" default: if raw["type"] == nil { return "Unknown operation type: undefined" } return fmt.Sprintf("Unknown operation type: %v", raw["type"]) } }