porting all github actions from bluesky-social/indigo to tangled CI
at main 24 kB view raw
1package lexicon 2 3import ( 4 "encoding/json" 5 "fmt" 6 "reflect" 7 "strings" 8 9 "github.com/bluesky-social/indigo/atproto/data" 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 12 "github.com/rivo/uniseg" 13) 14 15// Serialization helper type for top-level Lexicon schema JSON objects (files) 16type SchemaFile struct { 17 Lexicon int `json:"lexicon"` // must be 1 18 ID string `json:"id"` 19 Description *string `json:"description,omitempty"` 20 Defs map[string]SchemaDef `json:"defs"` 21} 22 23// enum type to represent any of the schema fields 24type SchemaDef struct { 25 Inner any 26} 27 28// Checks that the schema definition itself is valid (recursively). 29func (s *SchemaDef) CheckSchema() error { 30 switch v := s.Inner.(type) { 31 case SchemaRecord: 32 return v.CheckSchema() 33 case SchemaQuery: 34 return v.CheckSchema() 35 case SchemaProcedure: 36 return v.CheckSchema() 37 case SchemaSubscription: 38 return v.CheckSchema() 39 case SchemaNull: 40 return v.CheckSchema() 41 case SchemaBoolean: 42 return v.CheckSchema() 43 case SchemaInteger: 44 return v.CheckSchema() 45 case SchemaString: 46 return v.CheckSchema() 47 case SchemaBytes: 48 return v.CheckSchema() 49 case SchemaCIDLink: 50 return v.CheckSchema() 51 case SchemaArray: 52 return v.CheckSchema() 53 case SchemaObject: 54 return v.CheckSchema() 55 case SchemaBlob: 56 return v.CheckSchema() 57 case SchemaParams: 58 return v.CheckSchema() 59 case SchemaToken: 60 return v.CheckSchema() 61 case SchemaRef: 62 return v.CheckSchema() 63 case SchemaUnion: 64 return v.CheckSchema() 65 case SchemaUnknown: 66 return v.CheckSchema() 67 default: 68 return fmt.Errorf("unhandled schema type: %v", reflect.TypeOf(v)) 69 } 70} 71 72// Helper to recurse down the definition tree and set full references on any sub-schemas which need to embed that metadata 73func (s *SchemaDef) SetBase(base string) { 74 switch v := s.Inner.(type) { 75 case SchemaRecord: 76 for i, val := range v.Record.Properties { 77 val.SetBase(base) 78 v.Record.Properties[i] = val 79 } 80 s.Inner = v 81 case SchemaQuery: 82 for i, val := range v.Parameters.Properties { 83 val.SetBase(base) 84 v.Parameters.Properties[i] = val 85 } 86 if v.Output != nil && v.Output.Schema != nil { 87 v.Output.Schema.SetBase(base) 88 } 89 s.Inner = v 90 case SchemaProcedure: 91 for i, val := range v.Parameters.Properties { 92 val.SetBase(base) 93 v.Parameters.Properties[i] = val 94 } 95 if v.Input != nil && v.Input.Schema != nil { 96 v.Input.Schema.SetBase(base) 97 } 98 if v.Output != nil && v.Output.Schema != nil { 99 v.Output.Schema.SetBase(base) 100 } 101 s.Inner = v 102 case SchemaSubscription: 103 for i, val := range v.Parameters.Properties { 104 val.SetBase(base) 105 v.Parameters.Properties[i] = val 106 } 107 if v.Message != nil { 108 v.Message.Schema.SetBase(base) 109 } 110 s.Inner = v 111 case SchemaArray: 112 v.Items.SetBase(base) 113 s.Inner = v 114 case SchemaObject: 115 for i, val := range v.Properties { 116 val.SetBase(base) 117 v.Properties[i] = val 118 } 119 s.Inner = v 120 case SchemaParams: 121 for i, val := range v.Properties { 122 val.SetBase(base) 123 v.Properties[i] = val 124 } 125 s.Inner = v 126 case SchemaRef: 127 // add fully-qualified name 128 if strings.HasPrefix(v.Ref, "#") { 129 v.fullRef = base + v.Ref 130 } else { 131 v.fullRef = v.Ref 132 } 133 s.Inner = v 134 case SchemaUnion: 135 // add fully-qualified name 136 for _, ref := range v.Refs { 137 if strings.HasPrefix(ref, "#") { 138 ref = base + ref 139 } 140 v.fullRefs = append(v.fullRefs, ref) 141 } 142 s.Inner = v 143 } 144 return 145} 146 147func (s SchemaDef) MarshalJSON() ([]byte, error) { 148 return json.Marshal(s.Inner) 149} 150 151func (s *SchemaDef) UnmarshalJSON(b []byte) error { 152 t, err := ExtractTypeJSON(b) 153 if err != nil { 154 return err 155 } 156 // TODO: should we call CheckSchema here, instead of in lexicon loading? 157 switch t { 158 case "record": 159 v := new(SchemaRecord) 160 if err = json.Unmarshal(b, v); err != nil { 161 return err 162 } 163 s.Inner = *v 164 return nil 165 case "query": 166 v := new(SchemaQuery) 167 if err = json.Unmarshal(b, v); err != nil { 168 return err 169 } 170 s.Inner = *v 171 return nil 172 case "procedure": 173 v := new(SchemaProcedure) 174 if err = json.Unmarshal(b, v); err != nil { 175 return err 176 } 177 s.Inner = *v 178 return nil 179 case "subscription": 180 v := new(SchemaSubscription) 181 if err = json.Unmarshal(b, v); err != nil { 182 return err 183 } 184 s.Inner = *v 185 return nil 186 case "null": 187 v := new(SchemaNull) 188 if err = json.Unmarshal(b, v); err != nil { 189 return err 190 } 191 s.Inner = *v 192 return nil 193 case "boolean": 194 v := new(SchemaBoolean) 195 if err = json.Unmarshal(b, v); err != nil { 196 return err 197 } 198 s.Inner = *v 199 return nil 200 case "integer": 201 v := new(SchemaInteger) 202 if err = json.Unmarshal(b, v); err != nil { 203 return err 204 } 205 s.Inner = *v 206 return nil 207 case "string": 208 v := new(SchemaString) 209 if err = json.Unmarshal(b, v); err != nil { 210 return err 211 } 212 s.Inner = *v 213 return nil 214 case "bytes": 215 v := new(SchemaBytes) 216 if err = json.Unmarshal(b, v); err != nil { 217 return err 218 } 219 s.Inner = *v 220 return nil 221 case "cid-link": 222 v := new(SchemaCIDLink) 223 if err = json.Unmarshal(b, v); err != nil { 224 return err 225 } 226 s.Inner = *v 227 return nil 228 case "array": 229 v := new(SchemaArray) 230 if err = json.Unmarshal(b, v); err != nil { 231 return err 232 } 233 s.Inner = *v 234 return nil 235 case "object": 236 v := new(SchemaObject) 237 if err = json.Unmarshal(b, v); err != nil { 238 return err 239 } 240 s.Inner = *v 241 return nil 242 case "blob": 243 v := new(SchemaBlob) 244 if err = json.Unmarshal(b, v); err != nil { 245 return err 246 } 247 s.Inner = *v 248 return nil 249 case "params": 250 v := new(SchemaParams) 251 if err = json.Unmarshal(b, v); err != nil { 252 return err 253 } 254 s.Inner = *v 255 return nil 256 case "token": 257 v := new(SchemaToken) 258 if err = json.Unmarshal(b, v); err != nil { 259 return err 260 } 261 s.Inner = *v 262 return nil 263 case "ref": 264 v := new(SchemaRef) 265 if err = json.Unmarshal(b, v); err != nil { 266 return err 267 } 268 s.Inner = *v 269 return nil 270 case "union": 271 v := new(SchemaUnion) 272 if err = json.Unmarshal(b, v); err != nil { 273 return err 274 } 275 s.Inner = *v 276 return nil 277 case "unknown": 278 v := new(SchemaUnknown) 279 if err = json.Unmarshal(b, v); err != nil { 280 return err 281 } 282 s.Inner = *v 283 return nil 284 default: 285 return fmt.Errorf("unexpected schema type: %s", t) 286 } 287} 288 289type SchemaRecord struct { 290 Type string `json:"type"` // "record" 291 Description *string `json:"description,omitempty"` 292 Key string `json:"key"` 293 Record SchemaObject `json:"record"` 294} 295 296func (s *SchemaRecord) CheckSchema() error { 297 switch s.Key { 298 case "tid", "nsid", "any": 299 // pass 300 default: 301 if !strings.HasPrefix(s.Key, "literal:") { 302 return fmt.Errorf("invalid record key specifier: %s", s.Key) 303 } 304 } 305 return s.Record.CheckSchema() 306} 307 308type SchemaQuery struct { 309 Type string `json:"type"` // "query" 310 Description *string `json:"description,omitempty"` 311 Parameters SchemaParams `json:"parameters"` 312 Output *SchemaBody `json:"output"` 313 Errors []SchemaError `json:"errors,omitempty"` // optional 314} 315 316func (s *SchemaQuery) CheckSchema() error { 317 if s.Output != nil { 318 if err := s.Output.CheckSchema(); err != nil { 319 return err 320 } 321 } 322 for _, e := range s.Errors { 323 if err := e.CheckSchema(); err != nil { 324 return err 325 } 326 } 327 return s.Parameters.CheckSchema() 328} 329 330type SchemaProcedure struct { 331 Type string `json:"type"` // "procedure" 332 Description *string `json:"description,omitempty"` 333 Parameters SchemaParams `json:"parameters"` 334 Output *SchemaBody `json:"output"` // optional 335 Errors []SchemaError `json:"errors,omitempty"` // optional 336 Input *SchemaBody `json:"input"` // optional 337} 338 339func (s *SchemaProcedure) CheckSchema() error { 340 if s.Input != nil { 341 if err := s.Input.CheckSchema(); err != nil { 342 return err 343 } 344 } 345 if s.Output != nil { 346 if err := s.Output.CheckSchema(); err != nil { 347 return err 348 } 349 } 350 for _, e := range s.Errors { 351 if err := e.CheckSchema(); err != nil { 352 return err 353 } 354 } 355 return s.Parameters.CheckSchema() 356} 357 358type SchemaSubscription struct { 359 Type string `json:"type"` // "subscription" 360 Description *string `json:"description,omitempty"` 361 Parameters SchemaParams `json:"parameters"` 362 Message *SchemaMessage `json:"message,omitempty"` // TODO(specs): is this really optional? 363} 364 365func (s *SchemaSubscription) CheckSchema() error { 366 if s.Message != nil { 367 if err := s.Message.CheckSchema(); err != nil { 368 return err 369 } 370 } 371 return s.Parameters.CheckSchema() 372} 373 374type SchemaBody struct { 375 Description *string `json:"description,omitempty"` 376 Encoding string `json:"encoding"` // required, mimetype 377 Schema *SchemaDef `json:"schema"` // optional; type:object, type:ref, or type:union 378} 379 380func (s *SchemaBody) CheckSchema() error { 381 // TODO: any validation of encoding? 382 if s.Schema != nil { 383 switch s.Schema.Inner.(type) { 384 case SchemaObject, SchemaRef, SchemaUnion: 385 // pass 386 default: 387 return fmt.Errorf("body type can only have object, ref, or union schema") 388 } 389 if err := s.Schema.CheckSchema(); err != nil { 390 return err 391 } 392 } 393 return nil 394} 395 396type SchemaMessage struct { 397 Description *string `json:"description,omitempty"` 398 Schema SchemaDef `json:"schema"` // required; type:union only 399} 400 401func (s *SchemaMessage) CheckSchema() error { 402 if _, ok := s.Schema.Inner.(SchemaUnion); !ok { 403 return fmt.Errorf("message must have schema type union") 404 } 405 return s.Schema.CheckSchema() 406} 407 408type SchemaError struct { 409 Name string `json:"name"` 410 Description *string `json:"description"` 411} 412 413func (s *SchemaError) CheckSchema() error { 414 return nil 415} 416func (s *SchemaError) Validate(d any) error { 417 e, ok := d.(map[string]any) 418 if !ok { 419 return fmt.Errorf("expected an object in error position") 420 } 421 n, ok := e["error"] 422 if !ok { 423 return fmt.Errorf("expected error type") 424 } 425 if n != s.Name { 426 return fmt.Errorf("error type mis-match: %s", n) 427 } 428 return nil 429} 430 431type SchemaNull struct { 432 Type string `json:"type"` // "null" 433 Description *string `json:"description,omitempty"` 434} 435 436func (s *SchemaNull) CheckSchema() error { 437 return nil 438} 439 440func (s *SchemaNull) Validate(d any) error { 441 if d != nil { 442 return fmt.Errorf("expected null data, got: %s", reflect.TypeOf(d)) 443 } 444 return nil 445} 446 447type SchemaBoolean struct { 448 Type string `json:"type"` // "bool" 449 Description *string `json:"description,omitempty"` 450 Default *bool `json:"default,omitempty"` 451 Const *bool `json:"const,omitempty"` 452} 453 454func (s *SchemaBoolean) CheckSchema() error { 455 if s.Default != nil && s.Const != nil { 456 return fmt.Errorf("schema can't have both 'default' and 'const'") 457 } 458 return nil 459} 460 461func (s *SchemaBoolean) Validate(d any) error { 462 v, ok := d.(bool) 463 if !ok { 464 return fmt.Errorf("expected a boolean") 465 } 466 if s.Const != nil && v != *s.Const { 467 return fmt.Errorf("boolean val didn't match constant (%v): %v", *s.Const, v) 468 } 469 return nil 470} 471 472type SchemaInteger struct { 473 Type string `json:"type"` // "integer" 474 Description *string `json:"description,omitempty"` 475 Minimum *int `json:"minimum,omitempty"` 476 Maximum *int `json:"maximum,omitempty"` 477 Enum []int `json:"enum,omitempty"` 478 Default *int `json:"default,omitempty"` 479 Const *int `json:"const,omitempty"` 480} 481 482func (s *SchemaInteger) CheckSchema() error { 483 // TODO: enforce min/max against enum, default, const 484 if s.Default != nil && s.Const != nil { 485 return fmt.Errorf("schema can't have both 'default' and 'const'") 486 } 487 if s.Minimum != nil && s.Maximum != nil && *s.Maximum < *s.Minimum { 488 return fmt.Errorf("schema max < min") 489 } 490 return nil 491} 492 493func (s *SchemaInteger) Validate(d any) error { 494 v64, ok := d.(int64) 495 if !ok { 496 return fmt.Errorf("expected an integer") 497 } 498 v := int(v64) 499 if s.Const != nil && v != *s.Const { 500 return fmt.Errorf("integer val didn't match constant (%d): %d", *s.Const, v) 501 } 502 if (s.Minimum != nil && v < *s.Minimum) || (s.Maximum != nil && v > *s.Maximum) { 503 return fmt.Errorf("integer val outside specified range: %d", v) 504 } 505 if len(s.Enum) != 0 { 506 inEnum := false 507 for _, e := range s.Enum { 508 if e == v { 509 inEnum = true 510 break 511 } 512 } 513 if !inEnum { 514 return fmt.Errorf("integer val not in required enum: %d", v) 515 } 516 } 517 return nil 518} 519 520type SchemaString struct { 521 Type string `json:"type"` // "string" 522 Description *string `json:"description,omitempty"` 523 Format *string `json:"format,omitempty"` 524 MinLength *int `json:"minLength,omitempty"` 525 MaxLength *int `json:"maxLength,omitempty"` 526 MinGraphemes *int `json:"minGraphemes,omitempty"` 527 MaxGraphemes *int `json:"maxGraphemes,omitempty"` 528 KnownValues []string `json:"knownValues,omitempty"` 529 Enum []string `json:"enum,omitempty"` 530 Default *string `json:"default,omitempty"` 531 Const *string `json:"const,omitempty"` 532} 533 534func (s *SchemaString) CheckSchema() error { 535 // TODO: enforce min/max against enum, default, const 536 if s.Default != nil && s.Const != nil { 537 return fmt.Errorf("schema can't have both 'default' and 'const'") 538 } 539 if s.MinLength != nil && s.MaxLength != nil && *s.MaxLength < *s.MinLength { 540 return fmt.Errorf("schema max < min") 541 } 542 if s.MinGraphemes != nil && s.MaxGraphemes != nil && *s.MaxGraphemes < *s.MinGraphemes { 543 return fmt.Errorf("schema max < min") 544 } 545 if (s.MinLength != nil && *s.MinLength < 0) || 546 (s.MaxLength != nil && *s.MaxLength < 0) || 547 (s.MinGraphemes != nil && *s.MinGraphemes < 0) || 548 (s.MaxGraphemes != nil && *s.MaxGraphemes < 0) { 549 return fmt.Errorf("string schema min or max below zero") 550 } 551 if s.Format != nil { 552 switch *s.Format { 553 case "at-identifier", "at-uri", "cid", "datetime", "did", "handle", "nsid", "uri", "language", "tid", "record-key": 554 // pass 555 default: 556 return fmt.Errorf("unknown string format: %s", *s.Format) 557 } 558 } 559 return nil 560} 561 562func (s *SchemaString) Validate(d any, flags ValidateFlags) error { 563 v, ok := d.(string) 564 if !ok { 565 return fmt.Errorf("expected a string: %v", reflect.TypeOf(d)) 566 } 567 if s.Const != nil && v != *s.Const { 568 return fmt.Errorf("string val didn't match constant (%s): %s", *s.Const, v) 569 } 570 // TODO: is this actually counting UTF-8 length? 571 if (s.MinLength != nil && len(v) < *s.MinLength) || (s.MaxLength != nil && len(v) > *s.MaxLength) { 572 return fmt.Errorf("string length outside specified range: %d", len(v)) 573 } 574 if len(s.Enum) != 0 { 575 inEnum := false 576 for _, e := range s.Enum { 577 if e == v { 578 inEnum = true 579 break 580 } 581 } 582 if !inEnum { 583 return fmt.Errorf("string val not in required enum: %s", v) 584 } 585 } 586 if s.MinGraphemes != nil || s.MaxGraphemes != nil { 587 lenG := uniseg.GraphemeClusterCount(v) 588 if (s.MinGraphemes != nil && lenG < *s.MinGraphemes) || (s.MaxGraphemes != nil && lenG > *s.MaxGraphemes) { 589 return fmt.Errorf("string length (graphemes) outside specified range: %d", lenG) 590 } 591 } 592 if s.Format != nil { 593 switch *s.Format { 594 case "at-identifier": 595 if _, err := syntax.ParseAtIdentifier(v); err != nil { 596 return err 597 } 598 case "at-uri": 599 if _, err := syntax.ParseATURI(v); err != nil { 600 return err 601 } 602 case "cid": 603 if _, err := syntax.ParseCID(v); err != nil { 604 return err 605 } 606 case "datetime": 607 if flags&AllowLenientDatetime != 0 { 608 if _, err := syntax.ParseDatetimeLenient(v); err != nil { 609 return err 610 } 611 } else { 612 if _, err := syntax.ParseDatetime(v); err != nil { 613 return err 614 } 615 } 616 case "did": 617 if _, err := syntax.ParseDID(v); err != nil { 618 return err 619 } 620 case "handle": 621 if _, err := syntax.ParseHandle(v); err != nil { 622 return err 623 } 624 case "nsid": 625 if _, err := syntax.ParseNSID(v); err != nil { 626 return err 627 } 628 case "uri": 629 if _, err := syntax.ParseURI(v); err != nil { 630 return err 631 } 632 case "language": 633 if _, err := syntax.ParseLanguage(v); err != nil { 634 return err 635 } 636 case "tid": 637 if _, err := syntax.ParseTID(v); err != nil { 638 return err 639 } 640 case "record-key": 641 if _, err := syntax.ParseRecordKey(v); err != nil { 642 return err 643 } 644 } 645 } 646 return nil 647} 648 649type SchemaBytes struct { 650 Type string `json:"type"` // "bytes" 651 Description *string `json:"description,omitempty"` 652 MinLength *int `json:"minLength,omitempty"` 653 MaxLength *int `json:"maxLength,omitempty"` 654} 655 656func (s *SchemaBytes) CheckSchema() error { 657 if s.MinLength != nil && s.MaxLength != nil && *s.MaxLength < *s.MinLength { 658 return fmt.Errorf("schema max < min") 659 } 660 if (s.MinLength != nil && *s.MinLength < 0) || 661 (s.MaxLength != nil && *s.MaxLength < 0) { 662 return fmt.Errorf("bytes schema min or max below zero") 663 } 664 return nil 665} 666 667func (s *SchemaBytes) Validate(d any) error { 668 v, ok := d.(data.Bytes) 669 if !ok { 670 return fmt.Errorf("expecting bytes") 671 } 672 if (s.MinLength != nil && len(v) < *s.MinLength) || (s.MaxLength != nil && len(v) > *s.MaxLength) { 673 return fmt.Errorf("bytes size out of bounds: %d", len(v)) 674 } 675 return nil 676} 677 678type SchemaCIDLink struct { 679 Type string `json:"type"` // "cid-link" 680 Description *string `json:"description,omitempty"` 681} 682 683func (s *SchemaCIDLink) CheckSchema() error { 684 return nil 685} 686 687func (s *SchemaCIDLink) Validate(d any) error { 688 _, ok := d.(data.CIDLink) 689 if !ok { 690 return fmt.Errorf("expecting a cid-link") 691 } 692 return nil 693} 694 695type SchemaArray struct { 696 Type string `json:"type"` // "array" 697 Description *string `json:"description,omitempty"` 698 Items SchemaDef `json:"items"` 699 MinLength *int `json:"minLength,omitempty"` 700 MaxLength *int `json:"maxLength,omitempty"` 701} 702 703func (s *SchemaArray) CheckSchema() error { 704 if s.MinLength != nil && s.MaxLength != nil && *s.MaxLength < *s.MinLength { 705 return fmt.Errorf("schema max < min") 706 } 707 if (s.MinLength != nil && *s.MinLength < 0) || 708 (s.MaxLength != nil && *s.MaxLength < 0) { 709 return fmt.Errorf("array schema min or max below zero") 710 } 711 return s.Items.CheckSchema() 712} 713 714type SchemaObject struct { 715 Type string `json:"type"` // "object" 716 Description *string `json:"description,omitempty"` 717 Properties map[string]SchemaDef `json:"properties"` 718 Required []string `json:"required,omitempty"` 719 Nullable []string `json:"nullable,omitempty"` 720} 721 722func (s *SchemaObject) CheckSchema() error { 723 // TODO: check for set intersection between required and nullable 724 // TODO: check for set uniqueness of required and nullable 725 for _, k := range s.Required { 726 if _, ok := s.Properties[k]; !ok { 727 return fmt.Errorf("object 'required' field not in properties: %s", k) 728 } 729 } 730 for _, k := range s.Nullable { 731 if _, ok := s.Properties[k]; !ok { 732 return fmt.Errorf("object 'nullable' field not in properties: %s", k) 733 } 734 } 735 for k, def := range s.Properties { 736 // TODO: more checks on field name? 737 if len(k) == 0 { 738 return fmt.Errorf("empty object schema field name not allowed") 739 } 740 if err := def.CheckSchema(); err != nil { 741 return err 742 } 743 } 744 return nil 745} 746 747// Checks if a field name 'k' is one of the Nullable fields for this object 748func (s *SchemaObject) IsNullable(k string) bool { 749 for _, el := range s.Nullable { 750 if el == k { 751 return true 752 } 753 } 754 return false 755} 756 757type SchemaBlob struct { 758 Type string `json:"type"` // "blob" 759 Description *string `json:"description,omitempty"` 760 Accept []string `json:"accept,omitempty"` 761 MaxSize *int `json:"maxSize,omitempty"` 762} 763 764func (s *SchemaBlob) CheckSchema() error { 765 // TODO: validate Accept (mimetypes)? 766 if s.MaxSize != nil && *s.MaxSize <= 0 { 767 return fmt.Errorf("blob max size less or equal to zero") 768 } 769 return nil 770} 771 772func (s *SchemaBlob) Validate(d any, flags ValidateFlags) error { 773 v, ok := d.(data.Blob) 774 if !ok { 775 return fmt.Errorf("expected a blob") 776 } 777 if !(flags&AllowLegacyBlob != 0) && v.Size < 0 { 778 return fmt.Errorf("legacy blobs not allowed") 779 } 780 if len(s.Accept) > 0 { 781 typeOk := false 782 for _, pat := range s.Accept { 783 if acceptableMimeType(pat, v.MimeType) { 784 typeOk = true 785 break 786 } 787 } 788 if !typeOk { 789 return fmt.Errorf("blob mimetype doesn't match accepted: %s", v.MimeType) 790 } 791 } 792 if s.MaxSize != nil && int(v.Size) > *s.MaxSize { 793 return fmt.Errorf("blob size too large: %d", v.Size) 794 } 795 return nil 796} 797 798type SchemaParams struct { 799 Type string `json:"type"` // "params" 800 Description *string `json:"description,omitempty"` 801 Properties map[string]SchemaDef `json:"properties"` // boolean, integer, string, or unknown; or an array of these types 802 Required []string `json:"required,omitempty"` 803} 804 805func (s *SchemaParams) CheckSchema() error { 806 // TODO: check for set uniqueness of required 807 for _, k := range s.Required { 808 if _, ok := s.Properties[k]; !ok { 809 return fmt.Errorf("object 'required' field not in properties: %s", k) 810 } 811 } 812 for k, def := range s.Properties { 813 // TODO: more checks on field name? 814 if len(k) == 0 { 815 return fmt.Errorf("empty object schema field name not allowed") 816 } 817 switch v := def.Inner.(type) { 818 case SchemaBoolean, SchemaInteger, SchemaString, SchemaUnknown: 819 // pass 820 case SchemaArray: 821 switch v.Items.Inner.(type) { 822 case SchemaBoolean, SchemaInteger, SchemaString, SchemaUnknown: 823 // pass 824 default: 825 return fmt.Errorf("params array item type must be boolean, integer, string, or unknown") 826 } 827 default: 828 return fmt.Errorf("params field type must be boolean, integer, string, or unknown") 829 } 830 if err := def.CheckSchema(); err != nil { 831 return err 832 } 833 } 834 return nil 835} 836 837type SchemaToken struct { 838 Type string `json:"type"` // "token" 839 Description *string `json:"description,omitempty"` 840 // the fully-qualified identifier of this token 841 fullName string 842} 843 844func (s *SchemaToken) CheckSchema() error { 845 if s.fullName == "" { 846 return fmt.Errorf("expected fully-qualified token name") 847 } 848 return nil 849} 850 851func (s *SchemaToken) Validate(d any) error { 852 str, ok := d.(string) 853 if !ok { 854 return fmt.Errorf("expected a string for token, got: %s", reflect.TypeOf(d)) 855 } 856 if s.fullName == "" { 857 return fmt.Errorf("token name was not populated at parse time") 858 } 859 if str != s.fullName { 860 return fmt.Errorf("token name did not match expected: %s", str) 861 } 862 return nil 863} 864 865type SchemaRef struct { 866 Type string `json:"type"` // "ref" 867 Description *string `json:"description,omitempty"` 868 Ref string `json:"ref"` 869 // full path of reference 870 fullRef string 871} 872 873func (s *SchemaRef) CheckSchema() error { 874 // TODO: more validation of ref string? 875 if len(s.Ref) == 0 { 876 return fmt.Errorf("empty schema ref") 877 } 878 if len(s.fullRef) == 0 { 879 return fmt.Errorf("empty full schema ref") 880 } 881 return nil 882} 883 884type SchemaUnion struct { 885 Type string `json:"type"` // "union" 886 Description *string `json:"description,omitempty"` 887 Refs []string `json:"refs"` 888 Closed *bool `json:"closed,omitempty"` 889 // fully qualified 890 fullRefs []string 891} 892 893func (s *SchemaUnion) CheckSchema() error { 894 // TODO: uniqueness check on refs 895 for _, ref := range s.Refs { 896 // TODO: more validation of ref string? 897 if len(ref) == 0 { 898 return fmt.Errorf("empty schema ref") 899 } 900 } 901 if len(s.fullRefs) != len(s.Refs) { 902 return fmt.Errorf("union refs were not expanded") 903 } 904 return nil 905} 906 907type SchemaUnknown struct { 908 Type string `json:"type"` // "unknown" 909 Description *string `json:"description,omitempty"` 910} 911 912func (s *SchemaUnknown) CheckSchema() error { 913 return nil 914} 915 916func (s *SchemaUnknown) Validate(d any) error { 917 _, ok := d.(map[string]any) 918 if !ok { 919 return fmt.Errorf("'unknown' data must an object") 920 } 921 return nil 922}