1package lex
2
3import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "strings"
8)
9
10// Schema is a lexicon json file
11// e.g. atproto/lexicons/app/bsky/feed/post.json
12// https://atproto.com/specs/lexicon
13type Schema struct {
14 // path of json file read
15 path string
16
17 // prefix of lexicon group, e.g. "app.bsky" or "com.atproto"
18 prefix string
19
20 // Lexicon version, e.g. 1
21 Lexicon int `json:"lexicon"`
22 ID string `json:"id"`
23 Defs map[string]*TypeSchema `json:"defs"`
24}
25
26func ReadSchema(f string) (*Schema, error) {
27 fi, err := os.Open(f)
28 if err != nil {
29 return nil, err
30 }
31 defer fi.Close()
32
33 var s Schema
34 if err := json.NewDecoder(fi).Decode(&s); err != nil {
35 return nil, err
36 }
37 s.path = f
38
39 return &s, nil
40}
41
42func (s *Schema) Name() string {
43 p := strings.Split(s.ID, ".")
44 return p[len(p)-2] + p[len(p)-1]
45}
46
47func (s *Schema) AllTypes(prefix string, defMap map[string]*ExtDef) []outputType {
48 var out []outputType
49
50 var walk func(name string, ts *TypeSchema, needsCbor bool)
51 walk = func(name string, ts *TypeSchema, needsCbor bool) {
52 if ts == nil {
53 panic(fmt.Sprintf("nil type schema in %q (%s)", name, s.ID))
54 }
55
56 if needsCbor {
57 fmt.Println("Setting to record: ", name)
58 if name == "EmbedImages_View" {
59 panic("not ok")
60 }
61 ts.needsCbor = true
62 }
63
64 if name == "LabelDefs_SelfLabels" {
65 ts.needsType = true
66 }
67
68 ts.prefix = prefix
69 ts.id = s.ID
70 ts.defMap = defMap
71 if ts.Type == "object" ||
72 (ts.Type == "union" && len(ts.Refs) > 0) {
73 out = append(out, outputType{
74 Name: name,
75 Type: ts,
76 NeedsCbor: ts.needsCbor,
77 })
78
79 for _, r := range ts.Refs {
80 refname := r
81 if strings.HasPrefix(refname, "#") {
82 refname = s.ID + r
83 }
84
85 ed, ok := defMap[refname]
86 if !ok {
87 panic(fmt.Sprintf("cannot find: %q", refname))
88 }
89
90 fmt.Println("UNION REF", refname, name, needsCbor)
91
92 if needsCbor {
93 ed.Type.needsCbor = true
94 }
95
96 ed.Type.needsType = true
97 }
98 }
99
100 if ts.Type == "ref" {
101 refname := ts.Ref
102 if strings.HasPrefix(refname, "#") {
103 refname = s.ID + ts.Ref
104 }
105
106 sub, ok := defMap[refname]
107 if !ok {
108 panic(fmt.Sprintf("missing ref: %q", refname))
109 }
110
111 if needsCbor {
112 sub.Type.needsCbor = true
113 }
114 }
115
116 for childname, val := range ts.Properties {
117 walk(name+"_"+strings.Title(childname), val, ts.needsCbor)
118 }
119
120 if ts.Items != nil {
121 walk(name+"_Elem", ts.Items, ts.needsCbor)
122 }
123
124 if ts.Input != nil {
125 if ts.Input.Schema == nil {
126 if ts.Input.Encoding != EncodingCBOR &&
127 ts.Input.Encoding != EncodingANY &&
128 ts.Input.Encoding != EncodingCAR &&
129 ts.Input.Encoding != EncodingMP4 {
130 panic(fmt.Sprintf("strange input type def in %s", s.ID))
131 }
132 } else {
133 walk(name+"_Input", ts.Input.Schema, ts.needsCbor)
134 }
135 }
136
137 if ts.Output != nil {
138 if ts.Output.Schema == nil {
139 if ts.Output.Encoding != EncodingCBOR &&
140 ts.Output.Encoding != EncodingCAR &&
141 ts.Output.Encoding != EncodingANY &&
142 ts.Output.Encoding != EncodingJSONL &&
143 ts.Output.Encoding != EncodingMP4 {
144 panic(fmt.Sprintf("strange output type def in %s", s.ID))
145 }
146 } else {
147 walk(name+"_Output", ts.Output.Schema, ts.needsCbor)
148 }
149 }
150
151 if ts.Type == "record" {
152 ts.Record.needsType = true
153 walk(name, ts.Record, true)
154 }
155
156 }
157
158 tname := nameFromID(s.ID, prefix)
159
160 for name, def := range s.Defs {
161 n := tname + "_" + strings.Title(name)
162 if name == "main" {
163 n = tname
164 }
165 walk(n, def, def.needsCbor)
166 }
167
168 return out
169}