this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more lexgen progress

+468 -105
+52 -43
cmd/lexgen/flatschema.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "log/slog" 6 5 "slices" 7 6 "sort" 8 7 "strings" ··· 16 15 NSID syntax.NSID 17 16 Description *string 18 17 ExternalRefs map[string]bool // NSID with optional ref 19 - SelfRefs map[string]bool // def names 20 18 Defs map[string]FlatDef 21 19 Types []FlatType 22 20 } ··· 46 44 NSID: nsid, 47 45 Description: sf.Description, 48 46 ExternalRefs: map[string]bool{}, 49 - SelfRefs: map[string]bool{}, 50 47 Defs: map[string]FlatDef{}, 51 48 Types: []FlatType{}, 52 49 } ··· 106 103 Schema: def, 107 104 } 108 105 109 - // XXX: recurse 110 106 switch v := def.Inner.(type) { 111 107 case lexicon.SchemaRecord: 112 108 if err := fl.flattenObject(fd, tpath, &v.Record); err != nil { ··· 122 118 } 123 119 } 124 120 case lexicon.SchemaProcedure: 125 - // XXX 126 - /* 127 - if v.Output != nil && v.Output.Schema != nil { 128 - tp := slices.Clone(tpath) 129 - tp = append(tp, "output") 130 - if err := fl.flattenType(fd, tp, v.Output.Schema); err != nil { 131 - return err 132 - } 133 - } 134 - if v.Input != nil { 135 - props = v.Input 121 + // v.Properties: same as above 122 + if v.Output != nil && v.Output.Schema != nil { 123 + tp := slices.Clone(tpath) 124 + tp = append(tp, "output") 125 + if err := fl.flattenType(fd, tp, v.Output.Schema); err != nil { 126 + return err 136 127 } 137 - if v.Output != nil { 138 - props = v.Output 128 + } 129 + if v.Input != nil && v.Input.Schema != nil { 130 + tp := slices.Clone(tpath) 131 + tp = append(tp, "input") 132 + if err := fl.flattenType(fd, tp, v.Input.Schema); err != nil { 133 + return err 139 134 } 140 - */ 135 + } 141 136 case lexicon.SchemaSubscription: 142 - // XXX: 143 - /* 144 - if v.Message != nil { 145 - props = v.Message 137 + // v.Properties: same as above 138 + if v.Message != nil { 139 + switch vv := v.Message.Schema.Inner.(type) { 140 + case lexicon.SchemaUnion: 141 + for _, ref := range vv.Refs { 142 + if !strings.HasPrefix(ref, "#") { 143 + fl.ExternalRefs[strings.TrimSuffix(ref, "#main")] = true 144 + } 145 + } 146 + default: 147 + return fmt.Errorf("subscription with non-union message schema: %T", v.Message.Schema.Inner) 146 148 } 147 - */ 148 - case lexicon.SchemaArray: 149 - // XXX: isCompoundDef 149 + 150 + } 150 151 case lexicon.SchemaObject: 151 152 if err := fl.flattenObject(fd, tpath, &v); err != nil { 152 153 return err 153 154 } 154 155 case lexicon.SchemaRef: 155 156 if !strings.HasPrefix(v.Ref, "#") { 156 - fl.ExternalRefs[v.Ref] = true 157 + fl.ExternalRefs[strings.TrimSuffix(v.Ref, "#main")] = true 157 158 } 158 159 case lexicon.SchemaUnion: 159 160 for _, ref := range v.Refs { 160 161 if !strings.HasPrefix(ref, "#") { 161 - fl.ExternalRefs[ref] = true 162 + fl.ExternalRefs[strings.TrimSuffix(ref, "#main")] = true 162 163 } 163 164 } 164 - case lexicon.SchemaString, lexicon.SchemaNull, lexicon.SchemaInteger, lexicon.SchemaBoolean, lexicon.SchemaUnknown: 165 - // pass, don't emit (?) 165 + case lexicon.SchemaArray: 166 + // flatten the inner item 167 + tp := slices.Clone(tpath) 168 + tp = append(tp, "elem") 169 + if err := fl.flattenType(fd, tpath, &v.Items); err != nil { 170 + return err 171 + } 172 + // don't emit the array itself 173 + return nil 174 + case lexicon.SchemaString, lexicon.SchemaNull, lexicon.SchemaInteger, lexicon.SchemaBoolean, lexicon.SchemaUnknown, lexicon.SchemaBytes: 175 + // don't emit 176 + // NOTE: might want to emit some string "knownValue" lists in the future? 177 + return nil 178 + case lexicon.SchemaCIDLink, lexicon.SchemaBlob: 179 + // don't emit 166 180 return nil 167 181 case lexicon.SchemaToken: 168 - // pass 182 + // pass-through (emit) 183 + case lexicon.SchemaPermissionSet, lexicon.SchemaPermission: 184 + // pass-through (emit) 169 185 default: 170 186 return fmt.Errorf("unsupported def type for flattening (%s): %T", fd.Name, def.Inner) 171 187 } ··· 184 200 case lexicon.SchemaCIDLink, lexicon.SchemaBlob, lexicon.SchemaUnknown: 185 201 // no-op, but maybe set a flag on def? 186 202 case lexicon.SchemaArray: 203 + tp = append(tp, "elem") 187 204 if err := fl.flattenType(fd, tp, &v.Items); err != nil { 188 205 return err 189 206 } ··· 192 209 return err 193 210 } 194 211 case lexicon.SchemaRef: 195 - slog.Info("reference", "ref", v.Ref) 196 - if strings.HasPrefix(v.Ref, "#") { 197 - fl.SelfRefs[v.Ref] = true 198 - } else { 199 - // TODO: normalize #main refs? 200 - fl.ExternalRefs[v.Ref] = true 212 + if !strings.HasPrefix(v.Ref, "#") { 213 + // remove any #main suffix 214 + fl.ExternalRefs[strings.TrimSuffix(v.Ref, "#main")] = true 201 215 } 202 216 case lexicon.SchemaUnion: 203 - for _, ref := range v.Refs { 204 - if strings.HasPrefix(ref, "#") { 205 - fl.SelfRefs[ref] = true 206 - } else { 207 - // TODO: normalize #main refs? 208 - fl.ExternalRefs[ref] = true 209 - } 217 + if err := fl.flattenType(fd, tp, &field); err != nil { 218 + return err 210 219 } 211 220 default: 212 221 return fmt.Errorf("unsupported field type for object flattening: %T", field.Inner)
+280 -53
cmd/lexgen/generate.go
··· 3 3 import ( 4 4 "fmt" 5 5 "io" 6 + "log/slog" 6 7 "sort" 7 8 "strings" 8 9 ··· 15 16 PackageMappings map[string]string 16 17 // one of: "type-decoder", "map-string-any", "json-raw-message" 17 18 UnknownType string 19 + WarningText string 20 + } 21 + 22 + func LegacyConfig() *GenConfig { 23 + return &GenConfig{ 24 + // XXX RegisterLexiconTypeID: true, 25 + RegisterLexiconTypeID: false, 26 + UnknownType: "type-decoder", 27 + WarningText: "Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.", 28 + } 18 29 } 19 30 20 31 type FlatGenerator struct { 21 32 Config *GenConfig 22 33 Lex *FlatLexicon 34 + Cat lexicon.Catalog 23 35 Out io.Writer 24 36 } 25 37 26 38 func (gen *FlatGenerator) WriteLexicon() error { 27 39 28 - fmt.Fprintf(gen.Out, "// Auto-generated from Lexicon. DO NOT EDIT BY HAND.\n\n") 40 + if gen.Config.WarningText != "" { 41 + fmt.Fprintf(gen.Out, "// %s\n\n", gen.Config.WarningText) 42 + } 29 43 fmt.Fprintf(gen.Out, "package %s\n\n", gen.pkgName()) 30 44 fmt.Fprintf(gen.Out, "// schema: %s\n\n", gen.Lex.NSID) 31 45 fmt.Fprintln(gen.Out, "import (") ··· 35 49 fmt.Fprint(gen.Out, ")\n\n") 36 50 37 51 for _, ft := range gen.Lex.Types { 52 + slog.Info("generating type", "nsid", gen.Lex.NSID, "def", ft.DefName, "path", ft.Path) 38 53 if err := gen.WriteType(&ft); err != nil { 39 54 return err 40 55 } ··· 49 64 func (gen *FlatGenerator) baseName() string { 50 65 // TODO: memoize? 51 66 return nsidBaseName(gen.Lex.NSID) 67 + } 68 + 69 + func (gen *FlatGenerator) fileName() string { 70 + return nsidFileName(gen.Lex.NSID) + ".go" 52 71 } 53 72 54 73 func (gen *FlatGenerator) deps() map[string]bool { ··· 59 78 switch t.Type { 60 79 case "query", "procedure": 61 80 d["\"context\""] = true 81 + case "record": 82 + // TODO 83 + //d["cbg \"github.com/whyrusleeping/cbor-gen\""]: true 62 84 } 63 85 } 64 86 for ext, _ := range gen.Lex.ExternalRefs { 65 87 if strings.HasPrefix(ext, "com.atproto.") { 66 88 d["comatproto \"github.com/bluesky-social/indigo/api/atproto\""] = true 89 + } else if strings.HasPrefix(ext, "app.bsky.") { 90 + d["appbsky \"github.com/bluesky-social/indigo/api/bsky\""] = true 91 + } else if strings.HasPrefix(ext, "tools.ozone.") { 92 + d["toolsozone \"github.com/bluesky-social/indigo/api/toolsozone\""] = true 67 93 } else { 68 94 // XXX: handle more mappings; or log/error if missing 69 95 } ··· 74 100 func (gen *FlatGenerator) WriteType(ft *FlatType) error { 75 101 76 102 switch v := ft.Schema.Inner.(type) { 77 - // TODO: case lexicon.SchemaSubscription: 78 103 case lexicon.SchemaRecord: 79 104 if gen.Config.RegisterLexiconTypeID { 80 105 fmt.Fprintf(gen.Out, "func init() {\n") ··· 89 114 return err 90 115 } 91 116 case lexicon.SchemaQuery: 92 - return gen.writeQuery(ft, &v) 93 - // TODO: method 117 + return gen.writeEndpoint(ft, defDescription(ft.Schema), v.Parameters, v.Output, nil, false) 94 118 case lexicon.SchemaProcedure: 95 - // TODO: method 119 + return gen.writeEndpoint(ft, defDescription(ft.Schema), v.Parameters, v.Output, v.Input, true) 120 + case lexicon.SchemaSubscription: 121 + // pass; we only generate message types, not overall subscription 122 + case lexicon.SchemaPermissionSet, lexicon.SchemaPermission: 123 + // pass for Go codegen 96 124 case lexicon.SchemaToken: 97 125 // TODO: pass for now; could be a var/const? 98 126 case lexicon.SchemaString, lexicon.SchemaInteger, lexicon.SchemaBoolean, lexicon.SchemaUnknown: ··· 102 130 return err 103 131 } 104 132 case lexicon.SchemaUnion: 105 - // TODO: enum 133 + return gen.writeUnion(ft, &v) 106 134 case lexicon.SchemaRef: 107 - // TODO: pass for now. could be an alias type? 135 + // skip for now. could be an alias type? 108 136 default: 109 137 return fmt.Errorf("unhandled schema type for codegen: %T", ft.Schema.Inner) 110 138 } ··· 122 150 } 123 151 124 152 func (gen *FlatGenerator) fieldType(def *lexicon.SchemaDef, optional bool) (string, error) { 125 - // XXX more completeness here 153 + // NOTE: SchemaObject and SchemaUnion should be handled outside this function; as well as arrays of those types also count 154 + // TODO: another pass to check for type completeness 126 155 switch v := def.Inner.(type) { 127 156 case lexicon.SchemaNull: 128 - return "nil", nil 157 + // NOTE: using "any" as a generic 'nil' type 158 + return "any", nil 129 159 case lexicon.SchemaBoolean: 130 160 if optional { 131 161 return "*bool", nil ··· 134 164 } 135 165 case lexicon.SchemaInteger: 136 166 if optional { 137 - return "*int", nil 167 + return "*int64", nil 138 168 } else { 139 - return "int", nil 169 + return "int64", nil 140 170 } 141 171 case lexicon.SchemaString: 142 172 if optional { ··· 145 175 return "string", nil 146 176 } 147 177 case lexicon.SchemaBytes: 178 + // NOTE: not using a pointer for optional 179 + return "[]byte", nil 180 + case lexicon.SchemaCIDLink: 148 181 if optional { 149 - return "*[]byte", nil 182 + return "*lexutil.LexLink", nil 150 183 } else { 151 - return "[]byte", nil 184 + return "lexutil.LexLink", nil 152 185 } 153 - case lexicon.SchemaCIDLink: 186 + case lexicon.SchemaBlob: 154 187 if optional { 155 - return "*cid.Cid", nil 188 + return "*lexutil.LexBlob", nil 156 189 } else { 157 - return "cid.Cid", nil 190 + return "lexutil.LexBlob", nil 158 191 } 159 192 case lexicon.SchemaArray: 160 193 t, err := gen.fieldType(&v.Items, false) 161 194 if err != nil { 162 195 return "", err 163 196 } 164 - if optional { 165 - return "*[]" + t, nil 166 - } else { 167 - return "[]" + t, nil 168 - } 197 + // NOTE: not using a pointer for optional 198 + return "[]" + t, nil 169 199 case lexicon.SchemaUnknown: 170 200 switch gen.Config.UnknownType { 171 201 case "type-decoder": ··· 182 212 return "map[string]any", nil 183 213 } 184 214 case lexicon.SchemaRef: 215 + // XXX: this currently assumes that a struct is being referenced, but it might actually be something like an array or strings (with known values) 185 216 ptr := "" 186 217 if optional { 187 218 ptr = "*" 188 219 } 189 - if v.Ref == "#main" { 190 - return ptr + gen.baseName(), nil 191 - } 220 + 221 + // check for local references to concrete types first 192 222 if strings.HasPrefix(v.Ref, "#") { 193 - // local reference 194 - return fmt.Sprintf("%s%s_%s", ptr, gen.baseName(), strings.Title(v.Ref[1:])), nil 195 - } else { 196 - // external reference 197 - t, err := gen.externalRefType(v.Ref) 198 - if err != nil { 199 - return "", err 223 + dt, ok := gen.Lex.Defs[v.Ref[1:]] 224 + if !ok { 225 + return "", fmt.Errorf("broken self-reference: %s", v.Ref) 226 + } 227 + switch dt.Type { 228 + case "string": 229 + return ptr + "string", nil 230 + case "integer": 231 + return ptr + "int64", nil 232 + case "boolean": 233 + return ptr + "bool", nil 234 + // TODO: "unknown", "ref", "token", etc 235 + case "array": 236 + // TODO: more completeness here (eg, non-object types) 237 + return fmt.Sprintf("[]%s_%s_Elem", gen.baseName(), strings.Title(v.Ref[1:])), nil 238 + default: // presumed "object", "union" 239 + if v.Ref == "#main" { 240 + return ptr + gen.baseName(), nil 241 + } 242 + return fmt.Sprintf("%s%s_%s", ptr, gen.baseName(), strings.Title(v.Ref[1:])), nil 200 243 } 201 - return ptr + t, nil 244 + } 245 + 246 + // external reference 247 + t, err := gen.externalRefType(v.Ref) 248 + if err != nil { 249 + return "", err 202 250 } 251 + return ptr + t, nil 203 252 default: 204 253 return "", fmt.Errorf("unhandled schema type in struct field: %T", def.Inner) 205 254 } 206 255 } 207 256 208 257 func (gen *FlatGenerator) externalRefType(ref string) (string, error) { 258 + s, err := gen.Cat.Resolve(ref) 259 + if err != nil { 260 + return "", fmt.Errorf("could not resolve lexicon reference (%s): %w", ref, err) 261 + } 262 + 263 + switch s.Def.(type) { 264 + case lexicon.SchemaString: 265 + return "string", nil 266 + // XXX: other referenced types, like arrays? 267 + } 268 + 209 269 parts := strings.SplitN(ref, "#", 3) 210 270 if len(parts) > 2 { 211 271 return "", fmt.Errorf("failed to parse external ref: %s", ref) ··· 214 274 if err != nil { 215 275 return "", fmt.Errorf("failed to parse external ref NSID (%s): %w", ref, err) 216 276 } 277 + 278 + // check if this is actually in the same package (which might not mean the same NSID authority) 279 + if nsidPkgName(nsid) == gen.pkgName() { 280 + if len(parts) == 1 || parts[1] == "main" { 281 + return nsidBaseName(nsid), nil 282 + } else { 283 + return fmt.Sprintf("%s_%s", nsidBaseName(nsid), strings.Title(parts[1])), nil 284 + } 285 + } 286 + 217 287 if len(parts) == 1 || parts[1] == "main" { 218 288 return fmt.Sprintf("%s.%s", nsidPkgName(nsid), nsidBaseName(nsid)), nil 219 289 } else { ··· 232 302 } 233 303 234 304 if ft.DefName != "main" && len(ft.Path) == 0 { 235 - fmt.Fprintf(gen.Out, "// %s is a \"%s\" in the %s schema\n", name, ft.DefName, gen.Lex.NSID) 305 + fmt.Fprintf(gen.Out, "// %s is a \"%s\" in the %s schema.\n", name, ft.DefName, gen.Lex.NSID) 236 306 if obj.Description != nil { 237 307 fmt.Fprintln(gen.Out, "//") 238 308 } ··· 251 321 } 252 322 sort.Strings(fieldNames) 253 323 324 + // if this is a def-level struct, write out type decoder 325 + if len(ft.Path) == 0 { 326 + fmt.Fprintf(gen.Out, " LexiconTypeID string `json:\"$type\" cborgen:\"$type\"`\n") 327 + } 328 + 254 329 for _, fname := range fieldNames { 255 330 field := obj.Properties[fname] 256 331 optional := false ··· 263 338 var t string 264 339 var err error 265 340 266 - switch field.Inner.(type) { 341 + switch v := field.Inner.(type) { 267 342 case lexicon.SchemaObject, lexicon.SchemaUnion: 268 343 t = name + "_" + strings.Title(fname) 269 344 if optional { 270 345 t = "*" + t 271 346 } 347 + case lexicon.SchemaArray: 348 + switch v.Items.Inner.(type) { 349 + case lexicon.SchemaObject, lexicon.SchemaUnion: 350 + t = "[]" + name + "_" + strings.Title(fname) + "_Elem" 351 + // NOTE: not using ptr for optional 352 + default: 353 + t, err = gen.fieldType(&field, optional) 354 + if err != nil { 355 + return err 356 + } 357 + } 272 358 default: 273 359 t, err = gen.fieldType(&field, optional) 274 360 if err != nil { ··· 276 362 } 277 363 } 278 364 279 - fmt.Fprintf(gen.Out, " %s %s", strings.Title(fname), t) 365 + desc := defDescription(&field) 366 + if desc != "" { 367 + fmt.Fprintf(gen.Out, " // %s: %s\n", fname, desc) 368 + } 369 + fmt.Fprintf(gen.Out, " %s %s", strings.ReplaceAll(strings.Title(fname), "-", ""), t) 280 370 fmt.Fprintf(gen.Out, " `json:\"%s%s\" cborgen:\"%s%s\"`\n", fname, omitempty, fname, omitempty) 281 371 } 282 372 fmt.Fprintf(gen.Out, "}\n\n") ··· 284 374 return nil 285 375 } 286 376 287 - func (gen *FlatGenerator) writeQuery(ft *FlatType, query *lexicon.SchemaQuery) error { 377 + type unionRef struct { 378 + FieldName string 379 + TypeName string 380 + LexName string 381 + } 382 + 383 + func (gen *FlatGenerator) writeUnion(ft *FlatType, union *lexicon.SchemaUnion) error { 384 + 385 + name := gen.baseName() 386 + if ft.DefName != "main" { 387 + name += "_" + strings.Title(ft.DefName) 388 + } 389 + for _, sub := range ft.Path { 390 + name += "_" + strings.Title(sub) 391 + } 392 + 393 + unionRefs := map[string]unionRef{} 394 + refNames := []string{} 395 + for _, ref := range union.Refs { 396 + r := unionRef{ 397 + LexName: ref, 398 + } 399 + 400 + if strings.HasPrefix(ref, "#") { 401 + r.LexName = gen.Lex.NSID.String() + ref 402 + n := gen.baseName() 403 + if ref != "#main" { 404 + n += "_" + strings.Title(ref[1:]) 405 + } 406 + r.FieldName = n 407 + r.TypeName = n 408 + } else { 409 + n, err := gen.externalRefType(ref) 410 + if err != nil { 411 + return err 412 + } 413 + r.FieldName = n 414 + r.TypeName = n 415 + if strings.Contains(n, ".") { 416 + parts := strings.SplitN(n, ".", 2) 417 + r.FieldName = parts[1] 418 + } 419 + } 420 + refNames = append(refNames, r.FieldName) 421 + unionRefs[r.FieldName] = r 422 + } 423 + sort.Strings(refNames) 424 + 425 + // first print out the union struct type 426 + if union.Description != nil { 427 + for _, l := range strings.Split(*union.Description, "\n") { 428 + fmt.Fprintf(gen.Out, "// %s\n", l) 429 + } 430 + } 431 + fmt.Fprintf(gen.Out, "type %s struct {\n", name) 432 + 433 + for _, rname := range refNames { 434 + ref := unionRefs[rname] 435 + fmt.Fprintf(gen.Out, " %s *%s\n", ref.FieldName, ref.TypeName) 436 + } 437 + fmt.Fprintf(gen.Out, "}\n\n") 438 + 439 + return nil 440 + } 441 + 442 + func (gen *FlatGenerator) writeEndpoint(ft *FlatType, desc string, params *lexicon.SchemaParams, output, input *lexicon.SchemaBody, isProcedure bool) error { 288 443 name := gen.baseName() 289 444 290 445 fmt.Fprintf(gen.Out, "// %s calls the XRPC method \"%s\".\n", name, gen.Lex.NSID) 291 - if query.Description != nil { 446 + if desc != "" { 292 447 fmt.Fprintln(gen.Out, "//") 293 - for _, l := range strings.Split(*query.Description, "\n") { 448 + for _, l := range strings.Split(desc, "\n") { 294 449 fmt.Fprintf(gen.Out, "// %s\n", l) 295 450 } 296 451 } 297 452 298 453 outputBytes := false 299 454 outputStruct := "" 300 - if query.Output != nil && query.Output.Schema != nil { 301 - outputStruct = name + "_Output" 302 - } else if query.Output != nil && query.Output.Encoding != "" { 455 + if output != nil && output.Schema != nil { 456 + switch v := output.Schema.Inner.(type) { 457 + case lexicon.SchemaObject, lexicon.SchemaUnion: 458 + outputStruct = name + "_Output" 459 + case lexicon.SchemaRef: 460 + if strings.HasPrefix(v.Ref, "#") { 461 + // local reference 462 + outputStruct = fmt.Sprintf("%s_%s", gen.baseName(), strings.Title(v.Ref[1:])) 463 + } else { 464 + // external reference 465 + t, err := gen.externalRefType(v.Ref) 466 + if err != nil { 467 + return err 468 + } 469 + outputStruct = t 470 + } 471 + default: 472 + return fmt.Errorf("unsupported endpoint output schema def type: %T", output.Schema.Inner) 473 + } 474 + } else if output != nil && output.Encoding != "" { 303 475 outputBytes = true 304 476 } 305 477 306 478 paramNames := []string{} 307 - if query.Parameters != nil { 308 - for name := range query.Parameters.Properties { 479 + if params != nil { 480 + for name := range params.Properties { 309 481 paramNames = append(paramNames, name) 310 482 } 311 483 } 312 484 sort.Strings(paramNames) 313 485 314 - args := []string{"ctx context.Context"} 486 + args := []string{"ctx context.Context", "c lexutil.LexClient"} 315 487 reqParams := []string{} 316 488 optParams := []string{} 317 489 if len(paramNames) > 0 { 318 490 fmt.Fprintln(gen.Out, "//") 319 491 for _, name := range paramNames { 320 - param := query.Parameters.Properties[name] 492 + param := params.Properties[name] 321 493 ptr := "*" 322 - if isRequired(query.Parameters.Required, name) { 494 + if isRequired(params.Required, name) { 323 495 ptr = "" 324 496 reqParams = append(reqParams, name) 325 497 } else { ··· 341 513 fmt.Fprintf(gen.Out, "// %s: %s\n", name, *v.Description) 342 514 } 343 515 args = append(args, fmt.Sprintf("%s string", name)) 516 + case lexicon.SchemaUnknown: 517 + if v.Description != nil && *v.Description != "" { 518 + fmt.Fprintf(gen.Out, "// %s: %s\n", name, *v.Description) 519 + } 520 + args = append(args, fmt.Sprintf("%s any", name)) 344 521 case lexicon.SchemaArray: 345 522 if v.Description != nil && *v.Description != "" { 346 523 fmt.Fprintf(gen.Out, "// %s[]: %s\n", name, *v.Description) ··· 361 538 } 362 539 } 363 540 541 + inputArg := "nil" 542 + inputEncoding := "" 543 + inputStruct := "" 544 + if isProcedure && input != nil && input.Schema != nil { 545 + inputArg = "input" 546 + inputEncoding = input.Encoding 547 + switch v := input.Schema.Inner.(type) { 548 + case lexicon.SchemaObject, lexicon.SchemaUnion: 549 + inputStruct = name + "_Input" 550 + case lexicon.SchemaRef: 551 + if strings.HasPrefix(v.Ref, "#") { 552 + // local reference 553 + inputStruct = fmt.Sprintf("%s_%s", gen.baseName(), strings.Title(v.Ref[1:])) 554 + } else { 555 + // external reference 556 + t, err := gen.externalRefType(v.Ref) 557 + if err != nil { 558 + return err 559 + } 560 + inputStruct = t 561 + } 562 + } 563 + args = append(args, fmt.Sprintf("input *%s", inputStruct)) 564 + } else if isProcedure && input != nil && input.Encoding != "" { 565 + inputArg = "input" 566 + inputEncoding = input.Encoding 567 + args = append(args, "input io.Reader") 568 + } 569 + 364 570 doOutParam := "" 365 571 returnType := "" 366 572 fmt.Fprintf(gen.Out, "func %s(%s) ", name, strings.Join(args, ", ")) ··· 377 583 } else { 378 584 fmt.Fprintf(gen.Out, "error {\n") 379 585 doOutParam = "nil" 380 - returnType = "nil" 586 + } 587 + paramsArg := "nil" 588 + if params != nil && len(params.Properties) > 0 { 589 + paramsArg = "params" 590 + // TODO: switch to map[string]any 591 + fmt.Fprintf(gen.Out, " params := map[string]interface{}{}\n") 381 592 } 382 - // TODO: switch to map[string]any 383 - fmt.Fprintf(gen.Out, " params := map[string]interface{}{}\n") 384 593 for _, name := range optParams { 385 - param := query.Parameters.Properties[name] 594 + param := params.Properties[name] 386 595 switch param.Inner.(type) { 387 596 case lexicon.SchemaString: 388 597 fmt.Fprintf(gen.Out, " if %s != \"\" {\n", name) ··· 390 599 fmt.Fprintf(gen.Out, " }\n") 391 600 case lexicon.SchemaArray: 392 601 fmt.Fprintf(gen.Out, " if len(%s) > 0 {\n", name) 602 + fmt.Fprintf(gen.Out, " params[\"%s\"] = %s\n", name, name) 603 + fmt.Fprintf(gen.Out, " }\n") 604 + case lexicon.SchemaUnknown: 605 + fmt.Fprintf(gen.Out, " if %s != nil {\n", name) 393 606 fmt.Fprintf(gen.Out, " params[\"%s\"] = %s\n", name, name) 394 607 fmt.Fprintf(gen.Out, " }\n") 395 608 default: ··· 402 615 fmt.Fprintf(gen.Out, " params[\"%s\"] = %s\n", name, name) 403 616 } 404 617 fmt.Fprintln(gen.Out, "") 405 - fmt.Fprintf(gen.Out, " if err := c.LexDo(ctx, lexutil.Query, \"\", \"%s\", params, nil, %s); err != nil {\n", gen.Lex.NSID, doOutParam) 406 - fmt.Fprintf(gen.Out, " return nil, err\n") 618 + 619 + method := "lexutil.Query" 620 + if isProcedure { 621 + method = "lexutil.Procedure" 622 + } 623 + 624 + fmt.Fprintf(gen.Out, " if err := c.LexDo(ctx, %s, \"%s\", \"%s\", %s, %s, %s); err != nil {\n", method, inputEncoding, gen.Lex.NSID, paramsArg, inputArg, doOutParam) 625 + if returnType != "" { 626 + fmt.Fprintf(gen.Out, " return nil, err\n") 627 + } else { 628 + fmt.Fprintf(gen.Out, " return err\n") 629 + } 407 630 fmt.Fprintf(gen.Out, " }\n") 408 - fmt.Fprintf(gen.Out, " return %s, nil\n", returnType) 631 + if returnType != "" { 632 + fmt.Fprintf(gen.Out, " return %s, nil\n", returnType) 633 + } else { 634 + fmt.Fprintf(gen.Out, " return nil\n") 635 + } 409 636 fmt.Fprintf(gen.Out, "}\n\n") 410 637 411 638 return nil
+72 -9
cmd/lexgen/main.go
··· 1 1 package main 2 2 3 3 import ( 4 + "bytes" 4 5 "context" 5 6 "encoding/json" 6 7 "fmt" 8 + "go/format" 7 9 "io/fs" 8 10 "log/slog" 9 11 "os" ··· 16 18 17 19 "github.com/earthboundkid/versioninfo/v2" 18 20 "github.com/urfave/cli/v3" 21 + "golang.org/x/tools/imports" 19 22 ) 20 23 21 24 func main() { ··· 51 54 Usage: "base directory for project Lexicon files", 52 55 Sources: cli.EnvVars("LEXICONS_DIR"), 53 56 }, 54 - &cli.BoolFlag{ 55 - Name: "json", 56 - Usage: "output structured JSON", 57 + &cli.StringFlag{ 58 + Name: "output-dir", 59 + Value: "./lexgen-out/", 60 + Usage: "base directory for output packages", 61 + Sources: cli.EnvVars("OUTPUT_DIR"), 57 62 }, 58 63 }, 59 64 Action: runGenerate, ··· 69 74 } 70 75 } 71 76 72 - // TODO: load up entire directory in to a catalog? or have a "linter" struct? 77 + // load all directories 78 + cat := lexicon.NewBaseCatalog() 79 + lexDir := cmd.String("lexicons-dir") 80 + ldinfo, err := os.Stat(lexDir) 81 + if err == nil && ldinfo.IsDir() { 82 + if err := cat.LoadDirectory(lexDir); err != nil { 83 + return err 84 + } 85 + } 73 86 74 87 slog.Debug("starting lint run") 75 88 for _, p := range paths { ··· 78 91 return fmt.Errorf("failed loading %s: %w", p, err) 79 92 } 80 93 if finfo.IsDir() { 94 + if p != cmd.String("lexicons-dir") { 95 + // HACK: load first directory 96 + if err := cat.LoadDirectory(p); err != nil { 97 + return err 98 + } 99 + } 81 100 if err := filepath.WalkDir(p, func(fp string, d fs.DirEntry, err error) error { 82 101 if d.IsDir() || path.Ext(fp) != ".json" { 83 102 return nil 84 103 } 85 - return blah(ctx, cmd, fp) 104 + return genFile(ctx, cmd, &cat, fp) 86 105 }); err != nil { 87 106 return err 88 107 } 89 108 continue 90 109 } 91 - if err := blah(ctx, cmd, p); err != nil { 110 + if err := genFile(ctx, cmd, &cat, p); err != nil { 92 111 return err 93 112 } 94 113 } 95 114 return nil 96 115 } 97 116 98 - func blah(ctx context.Context, cmd *cli.Command, p string) error { 117 + func genFile(ctx context.Context, cmd *cli.Command, cat lexicon.Catalog, p string) error { 99 118 b, err := os.ReadFile(p) 100 119 if err != nil { 101 - return err 120 + return fmt.Errorf("failed to read lexicon schema from disk (%s): %w", p, err) 102 121 } 103 122 104 123 // parse file regularly ··· 111 130 err = sf.FinishParse() 112 131 } 113 132 if err != nil { 133 + return fmt.Errorf("failed to parse lexicon schema from disk (%s): %w", p, err) 134 + } 135 + 136 + flat, err := FlattenSchemaFile(&sf) 137 + if err != nil { 138 + return fmt.Errorf("internal codegen flattening error (%s): %w", p, err) 139 + } 140 + 141 + buf := new(bytes.Buffer) 142 + 143 + gen := FlatGenerator{ 144 + Config: LegacyConfig(), 145 + Lex: flat, 146 + Cat: cat, 147 + Out: buf, 148 + } 149 + if err := gen.WriteLexicon(); err != nil { 150 + return fmt.Errorf("failed to format codegen output (%s): %w", p, err) 151 + } 152 + 153 + outPath := path.Join(cmd.String("output-dir"), gen.pkgName(), gen.fileName()) 154 + if err := os.MkdirAll(path.Dir(outPath), 0755); err != nil { 114 155 return err 115 156 } 116 - return nil 157 + 158 + fixImports := false 159 + if fixImports { 160 + fmtOpts := imports.Options{ 161 + Comments: true, 162 + TabIndent: false, 163 + TabWidth: 4, 164 + } 165 + //_ = fmtOpts 166 + formatted, err := imports.Process(outPath, buf.Bytes(), &fmtOpts) 167 + if err != nil { 168 + return fmt.Errorf("failed to format codegen output (%s): %w", p, err) 169 + } 170 + return os.WriteFile(outPath, formatted, 0644) 171 + } else { 172 + formatted, err := format.Source(buf.Bytes()) 173 + if err != nil { 174 + return fmt.Errorf("failed to format codegen output (%s): %w", p, err) 175 + } 176 + return os.WriteFile(outPath, formatted, 0644) 177 + } 117 178 } 118 179 119 180 var cmdDev = &cli.Command{ ··· 154 215 return err 155 216 } 156 217 218 + cat := lexicon.NewBaseCatalog() 157 219 gen := FlatGenerator{ 158 220 Config: &GenConfig{ 159 221 RegisterLexiconTypeID: true, 160 222 }, 223 + Cat: &cat, 161 224 Lex: flat, 162 225 Out: os.Stdout, 163 226 }
+64
cmd/lexgen/util.go
··· 58 58 } 59 59 } 60 60 61 + func defDescription(sd *lexicon.SchemaDef) string { 62 + var desc *string 63 + 64 + switch v := sd.Inner.(type) { 65 + case lexicon.SchemaRecord: 66 + desc = v.Description 67 + case lexicon.SchemaQuery: 68 + desc = v.Description 69 + case lexicon.SchemaProcedure: 70 + desc = v.Description 71 + case lexicon.SchemaSubscription: 72 + desc = v.Description 73 + case lexicon.SchemaPermissionSet: 74 + // TODO: extract *some* description? 75 + case lexicon.SchemaPermission: 76 + desc = v.Description 77 + case lexicon.SchemaNull: 78 + desc = v.Description 79 + case lexicon.SchemaBoolean: 80 + desc = v.Description 81 + case lexicon.SchemaInteger: 82 + desc = v.Description 83 + case lexicon.SchemaString: 84 + desc = v.Description 85 + case lexicon.SchemaBytes: 86 + desc = v.Description 87 + case lexicon.SchemaCIDLink: 88 + desc = v.Description 89 + case lexicon.SchemaArray: 90 + desc = v.Description 91 + case lexicon.SchemaObject: 92 + desc = v.Description 93 + case lexicon.SchemaBlob: 94 + desc = v.Description 95 + case lexicon.SchemaParams: 96 + desc = v.Description 97 + case lexicon.SchemaToken: 98 + desc = v.Description 99 + case lexicon.SchemaRef: 100 + desc = v.Description 101 + case lexicon.SchemaUnion: 102 + desc = v.Description 103 + case lexicon.SchemaUnknown: 104 + desc = v.Description 105 + } 106 + if desc != nil && *desc != "" { 107 + return *desc 108 + } 109 + return "" 110 + } 111 + 61 112 func isCompoundDef(sd *lexicon.SchemaDef) bool { 62 113 switch sd.Inner.(type) { 63 114 case lexicon.SchemaRecord, lexicon.SchemaQuery, lexicon.SchemaProcedure, lexicon.SchemaSubscription, lexicon.SchemaArray, lexicon.SchemaObject, lexicon.SchemaUnion: ··· 94 145 } 95 146 return strings.Join(parts, "") 96 147 } 148 + 149 + func nsidFileName(nsid syntax.NSID) string { 150 + domain := strings.ToLower(nsid.Authority()) 151 + reg, err := publicsuffix.EffectiveTLDPlusOne(domain) 152 + if err != nil { 153 + return "FAIL" 154 + } 155 + rem := domain[0 : len(domain)-len(reg)] 156 + parts := strings.Split(rem, ".") 157 + slices.Reverse(parts) 158 + parts = append(parts, nsid.Name()) 159 + return strings.Join(parts, "") 160 + }