···50505151// Inserts a schema loaded from a JSON file in to the catalog.
5252func (c *BaseCatalog) AddSchemaFile(sf SchemaFile) error {
5353- if sf.Lexicon != 1 {
5454- return fmt.Errorf("unsupported lexicon language version: %d", sf.Lexicon)
5353+5454+ if err := sf.CheckSchema(); err != nil {
5555+ return err
5556 }
5757+5658 base := sf.ID
5759 for frag, def := range sf.Defs {
5858- if len(frag) == 0 || strings.Contains(frag, "#") || strings.Contains(frag, ".") {
5959- // TODO: more validation here?
6060- return fmt.Errorf("schema name invalid: %s", frag)
6161- }
6260 name := base + "#" + frag
6361 if _, ok := c.schemas[name]; ok {
6462 return fmt.Errorf("catalog already contained a schema with name: %s", name)
6563 }
6666- // "A file can have at most one definition with one of the "primary" types. Primary types should always have the name main. It is possible for main to describe a non-primary type."
6767- switch s := def.Inner.(type) {
6868- case SchemaRecord, SchemaQuery, SchemaProcedure, SchemaSubscription, SchemaPermissionSet:
6969- if frag != "main" {
7070- return fmt.Errorf("record, query, procedure, and subscription types must be 'main', not: %s", frag)
7171- }
7272- case SchemaToken:
7373- // add fully-qualified name to token
7474- s.fullName = name
7575- def.Inner = s
7676- }
7777- def.SetBase(base)
7878- if err := def.CheckSchema(); err != nil {
7979- return err
8080- }
8164 s := Schema{
8265 ID: name,
8366 Def: def.Inner,
···9174func (c *BaseCatalog) addSchemaFromBytes(b []byte) error {
9275 var sf SchemaFile
9376 if err := json.Unmarshal(b, &sf); err != nil {
7777+ return err
7878+ }
7979+ if err := sf.FinishParse(); err != nil {
9480 return err
9581 }
9682 if err := c.AddSchemaFile(sf); err != nil {
-8
atproto/lexicon/language.go
···1212 "github.com/rivo/uniseg"
1313)
14141515-// Serialization helper type for top-level Lexicon schema JSON objects (files)
1616-type SchemaFile struct {
1717- Lexicon int `json:"lexicon"` // must be 1
1818- ID string `json:"id"`
1919- Description *string `json:"description,omitempty"`
2020- Defs map[string]SchemaDef `json:"defs"`
2121-}
2222-2315// enum type to represent any of the schema fields
2416type SchemaDef struct {
2517 Inner any
+68
atproto/lexicon/schemafile.go
···11+package lexicon
22+33+import (
44+ "fmt"
55+ "strings"
66+)
77+88+// Serialization helper type for top-level Lexicon schema JSON objects (files).
99+//
1010+// Note that the [FinishParse] method should always be called after unmarshalling a SchemaFile from JSON.
1111+type SchemaFile struct {
1212+ Lexicon int `json:"lexicon"` // must be 1
1313+ ID string `json:"id"`
1414+ Description *string `json:"description,omitempty"`
1515+ Defs map[string]SchemaDef `json:"defs"`
1616+}
1717+1818+// Helper method which should always be called after parsing a schema file (eg, from JSON).
1919+//
2020+// Does some very basic validation (eg, lexicon language version), and fills in
2121+// internal references (for example full name of tokens).
2222+func (sf *SchemaFile) FinishParse() error {
2323+ if sf.Lexicon != 1 {
2424+ return fmt.Errorf("unsupported lexicon language version: %d", sf.Lexicon)
2525+ }
2626+ base := sf.ID
2727+ for frag, def := range sf.Defs {
2828+ if len(frag) == 0 || strings.Contains(frag, "#") || strings.Contains(frag, ".") {
2929+ // TODO: more validation here?
3030+ return fmt.Errorf("schema name invalid: %s", frag)
3131+ }
3232+ name := base + "#" + frag
3333+ switch s := def.Inner.(type) {
3434+ case SchemaToken:
3535+ // add fully-qualified name to token
3636+ s.FullName = name
3737+ def.Inner = s
3838+ }
3939+ def.SetBase(base)
4040+ sf.Defs[frag] = def
4141+ }
4242+ return nil
4343+}
4444+4545+// Calls [SchemaDef.CheckSchema] recursively over all defs
4646+func (sf *SchemaFile) CheckSchema() error {
4747+ if sf.Lexicon != 1 {
4848+ return fmt.Errorf("unsupported lexicon language version: %d", sf.Lexicon)
4949+ }
5050+5151+ for frag, def := range sf.Defs {
5252+ if len(frag) == 0 || strings.Contains(frag, "#") || strings.Contains(frag, ".") {
5353+ // TODO: more validation here?
5454+ return fmt.Errorf("schema name invalid: %s", frag)
5555+ }
5656+ // "A file can have at most one definition with one of the "primary" types. Primary types should always have the name main. It is possible for main to describe a non-primary type."
5757+ switch def.Inner.(type) {
5858+ case SchemaRecord, SchemaQuery, SchemaProcedure, SchemaSubscription, SchemaPermissionSet:
5959+ if frag != "main" {
6060+ return fmt.Errorf("record, query, procedure, and subscription types must be 'main', not: %s", frag)
6161+ }
6262+ }
6363+ if err := def.CheckSchema(); err != nil {
6464+ return err
6565+ }
6666+ }
6767+ return nil
6868+}