|
36 | 36 | typeBoolean = "boolean" |
37 | 37 | typeObject = "object" |
38 | 38 | typeArray = "array" |
| 39 | + typeNull = "null" |
39 | 40 |
|
40 | 41 | formatDate = "date" |
41 | 42 | formatDateTime = "date-time" |
@@ -127,6 +128,23 @@ func (g *JSONSchemaGenerator) formatFieldName(field *protogen.Field) string { |
127 | 128 | return field.Desc.JSONName() |
128 | 129 | } |
129 | 130 |
|
| 131 | +func (g *JSONSchemaGenerator) formatOneofFieldName(oneof *protogen.Oneof) string { |
| 132 | + if *g.conf.Naming == "proto" { |
| 133 | + return string(oneof.Desc.Name()) |
| 134 | + } |
| 135 | + |
| 136 | + name := oneof.GoName |
| 137 | + if len(name) > 1 { |
| 138 | + return strings.ToLower(name[0:1]) + name[1:] |
| 139 | + } |
| 140 | + |
| 141 | + if len(name) == 1 { |
| 142 | + return strings.ToLower(name) |
| 143 | + } |
| 144 | + |
| 145 | + return name |
| 146 | +} |
| 147 | + |
130 | 148 | // messageDefinitionName builds the full schema definition name of a message. |
131 | 149 | func messageDefinitionName(desc protoreflect.MessageDescriptor) string { |
132 | 150 | name := string(desc.Name()) |
@@ -262,31 +280,158 @@ func (g *JSONSchemaGenerator) schemaOrReferenceForField(field protoreflect.Field |
262 | 280 | return kindSchema |
263 | 281 | } |
264 | 282 |
|
| 283 | +func (g *JSONSchemaGenerator) namedSchemaForField(field *protogen.Field, schema *jsonschema.NamedSchema, isValueProp bool) *jsonschema.NamedSchema { |
| 284 | + // The field is either described by a reference or a schema. |
| 285 | + fieldSchema := g.schemaOrReferenceForField(field.Desc, schema.Value.Definitions) |
| 286 | + if fieldSchema == nil { |
| 287 | + return nil |
| 288 | + } |
| 289 | + |
| 290 | + // Handle readonly and writeonly properties, if the schema version can handle it. |
| 291 | + if getSchemaVersion(schema.Value) >= "07" { |
| 292 | + t := true |
| 293 | + // Check the field annotations to see if this is a readonly field. |
| 294 | + extension := proto.GetExtension(field.Desc.Options(), annotations.E_FieldBehavior) |
| 295 | + if extension != nil { |
| 296 | + switch v := extension.(type) { |
| 297 | + case []annotations.FieldBehavior: |
| 298 | + for _, vv := range v { |
| 299 | + if vv == annotations.FieldBehavior_OUTPUT_ONLY { |
| 300 | + fieldSchema.ReadOnly = &t |
| 301 | + } else if vv == annotations.FieldBehavior_INPUT_ONLY { |
| 302 | + fieldSchema.WriteOnly = &t |
| 303 | + } |
| 304 | + } |
| 305 | + default: |
| 306 | + log.Printf("unsupported extension type %T", extension) |
| 307 | + } |
| 308 | + } |
| 309 | + } |
| 310 | + |
| 311 | + fieldName := "value" |
| 312 | + if !isValueProp { |
| 313 | + fieldName = g.formatFieldName(field) |
| 314 | + } |
| 315 | + |
| 316 | + // Do not add title for ref values |
| 317 | + if fieldSchema.Ref == nil { |
| 318 | + fieldSchema.Title = &fieldName |
| 319 | + } |
| 320 | + |
| 321 | + // Get the field description from the comments. |
| 322 | + description := g.filterCommentString(field.Comments.Leading, true) |
| 323 | + if description != "" { |
| 324 | + // Note: Description will be ignored if $ref is set, but is still useful |
| 325 | + fieldSchema.Description = &description |
| 326 | + } |
| 327 | + |
| 328 | + return &jsonschema.NamedSchema{ |
| 329 | + Name: fieldName, |
| 330 | + Value: fieldSchema, |
| 331 | + } |
| 332 | +} |
| 333 | + |
| 334 | +func (g *JSONSchemaGenerator) setupSchemaForMessage(schemaName string, comments protogen.Comments) *jsonschema.NamedSchema { |
| 335 | + typ := "object" |
| 336 | + id := fmt.Sprintf("%s%s.json", *g.conf.BaseURL, schemaName) |
| 337 | + |
| 338 | + schema := &jsonschema.NamedSchema{ |
| 339 | + Name: schemaName, |
| 340 | + Value: &jsonschema.Schema{ |
| 341 | + Schema: g.conf.Version, |
| 342 | + ID: &id, |
| 343 | + Type: &jsonschema.StringOrStringArray{String: &typ}, |
| 344 | + Title: &schemaName, |
| 345 | + Properties: &[]*jsonschema.NamedSchema{}, |
| 346 | + }, |
| 347 | + } |
| 348 | + |
| 349 | + description := g.filterCommentString(comments, true) |
| 350 | + if description != "" { |
| 351 | + schema.Value.Description = &description |
| 352 | + } |
| 353 | + |
| 354 | + return schema |
| 355 | +} |
| 356 | + |
| 357 | +func (g *JSONSchemaGenerator) buildKindProperty(propertyValue string) *jsonschema.NamedSchema { |
| 358 | + kind := "kind" |
| 359 | + kindProperty := &jsonschema.NamedSchema{ |
| 360 | + Name: kind, |
| 361 | + Value: &jsonschema.Schema{ |
| 362 | + Title: &kind, |
| 363 | + Type: &jsonschema.StringOrStringArray{String: &typeString}, |
| 364 | + Enumeration: &[]jsonschema.SchemaEnumValue{}, |
| 365 | + }, |
| 366 | + } |
| 367 | + *kindProperty.Value.Enumeration = append( |
| 368 | + *kindProperty.Value.Enumeration, |
| 369 | + jsonschema.SchemaEnumValue{String: &propertyValue}, |
| 370 | + ) |
| 371 | + return kindProperty |
| 372 | +} |
| 373 | + |
| 374 | +func (g *JSONSchemaGenerator) addOneofFieldsToSchema(oneofs []*protogen.Oneof, schema *jsonschema.NamedSchema) { |
| 375 | + if oneofs == nil { |
| 376 | + return |
| 377 | + } |
| 378 | + |
| 379 | + for _, oneOfProto := range oneofs { |
| 380 | + oneOfSchema := jsonschema.Schema{ |
| 381 | + OneOf: &[]*jsonschema.Schema{}, |
| 382 | + } |
| 383 | + |
| 384 | + *oneOfSchema.OneOf = append(*oneOfSchema.OneOf, &jsonschema.Schema{Type: &jsonschema.StringOrStringArray{String: &typeNull}}) |
| 385 | + |
| 386 | + for _, fieldProto := range oneOfProto.Fields { |
| 387 | + ref := schema.Name + "_" + fieldProto.GoName |
| 388 | + oneofFieldSchema := &jsonschema.NamedSchema{ |
| 389 | + Name: ref, |
| 390 | + Value: &jsonschema.Schema{ |
| 391 | + Type: &jsonschema.StringOrStringArray{String: &typeObject}, |
| 392 | + Title: &ref, |
| 393 | + Properties: &[]*jsonschema.NamedSchema{}, |
| 394 | + }, |
| 395 | + } |
| 396 | + kindProperty := g.buildKindProperty(string(fieldProto.Desc.Name())) |
| 397 | + actualProperty := g.namedSchemaForField(fieldProto, schema, true) |
| 398 | + if actualProperty == nil { |
| 399 | + continue |
| 400 | + } |
| 401 | + |
| 402 | + *oneofFieldSchema.Value.Properties = append( |
| 403 | + *oneofFieldSchema.Value.Properties, |
| 404 | + kindProperty, |
| 405 | + actualProperty, |
| 406 | + ) |
| 407 | + |
| 408 | + if schema.Value.Definitions == nil { |
| 409 | + schema.Value.Definitions = &[]*jsonschema.NamedSchema{} |
| 410 | + } |
| 411 | + *schema.Value.Definitions = append(*schema.Value.Definitions, oneofFieldSchema) |
| 412 | + |
| 413 | + definitionsRef := "#/definitions/" + ref |
| 414 | + *oneOfSchema.OneOf = append(*oneOfSchema.OneOf, &jsonschema.Schema{Ref: &definitionsRef}) |
| 415 | + } |
| 416 | + |
| 417 | + *schema.Value.Properties = append( |
| 418 | + *schema.Value.Properties, |
| 419 | + &jsonschema.NamedSchema{ |
| 420 | + Name: g.formatOneofFieldName(oneOfProto), |
| 421 | + Value: &oneOfSchema, |
| 422 | + }, |
| 423 | + ) |
| 424 | + } |
| 425 | +} |
| 426 | + |
265 | 427 | // buildSchemasFromMessages creates a schema for each message. |
266 | 428 | func (g *JSONSchemaGenerator) buildSchemasFromMessages(messages []*protogen.Message) []*jsonschema.NamedSchema { |
267 | 429 | schemas := []*jsonschema.NamedSchema{} |
268 | 430 |
|
269 | 431 | // For each message, generate a schema. |
270 | 432 | for _, message := range messages { |
271 | 433 | schemaName := string(message.Desc.Name()) |
272 | | - typ := "object" |
273 | | - id := fmt.Sprintf("%s%s.json", *g.conf.BaseURL, schemaName) |
274 | | - |
275 | | - schema := &jsonschema.NamedSchema{ |
276 | | - Name: schemaName, |
277 | | - Value: &jsonschema.Schema{ |
278 | | - Schema: g.conf.Version, |
279 | | - ID: &id, |
280 | | - Type: &jsonschema.StringOrStringArray{String: &typ}, |
281 | | - Title: &schemaName, |
282 | | - Properties: &[]*jsonschema.NamedSchema{}, |
283 | | - }, |
284 | | - } |
285 | | - |
286 | | - description := g.filterCommentString(message.Comments.Leading, true) |
287 | | - if description != "" { |
288 | | - schema.Value.Description = &description |
289 | | - } |
| 434 | + schema := g.setupSchemaForMessage(schemaName, message.Comments.Leading) |
290 | 435 |
|
291 | 436 | // Any embedded messages will be created as definitions |
292 | 437 | if message.Messages != nil { |
@@ -316,54 +461,22 @@ func (g *JSONSchemaGenerator) buildSchemasFromMessages(messages []*protogen.Mess |
316 | 461 | if message.Desc.IsMapEntry() { |
317 | 462 | continue |
318 | 463 | } |
| 464 | + |
| 465 | + g.addOneofFieldsToSchema(message.Oneofs, schema) |
319 | 466 |
|
320 | 467 | for _, field := range message.Fields { |
321 | | - // The field is either described by a reference or a schema. |
322 | | - fieldSchema := g.schemaOrReferenceForField(field.Desc, schema.Value.Definitions) |
323 | | - if fieldSchema == nil { |
| 468 | + if field.Oneof != nil { |
324 | 469 | continue |
325 | 470 | } |
326 | 471 |
|
327 | | - // Handle readonly and writeonly properties, if the schema version can handle it. |
328 | | - if getSchemaVersion(schema.Value) >= "07" { |
329 | | - t := true |
330 | | - // Check the field annotations to see if this is a readonly field. |
331 | | - extension := proto.GetExtension(field.Desc.Options(), annotations.E_FieldBehavior) |
332 | | - if extension != nil { |
333 | | - switch v := extension.(type) { |
334 | | - case []annotations.FieldBehavior: |
335 | | - for _, vv := range v { |
336 | | - if vv == annotations.FieldBehavior_OUTPUT_ONLY { |
337 | | - fieldSchema.ReadOnly = &t |
338 | | - } else if vv == annotations.FieldBehavior_INPUT_ONLY { |
339 | | - fieldSchema.WriteOnly = &t |
340 | | - } |
341 | | - } |
342 | | - default: |
343 | | - log.Printf("unsupported extension type %T", extension) |
344 | | - } |
345 | | - } |
346 | | - } |
347 | | - |
348 | | - fieldName := g.formatFieldName(field) |
349 | | - // Do not add title for ref values |
350 | | - if fieldSchema.Ref == nil { |
351 | | - fieldSchema.Title = &fieldName |
352 | | - } |
353 | | - |
354 | | - // Get the field description from the comments. |
355 | | - description := g.filterCommentString(field.Comments.Leading, true) |
356 | | - if description != "" { |
357 | | - // Note: Description will be ignored if $ref is set, but is still useful |
358 | | - fieldSchema.Description = &description |
| 472 | + namedSchema := g.namedSchemaForField(field, schema, false) |
| 473 | + if namedSchema == nil { |
| 474 | + continue |
359 | 475 | } |
360 | 476 |
|
361 | 477 | *schema.Value.Properties = append( |
362 | 478 | *schema.Value.Properties, |
363 | | - &jsonschema.NamedSchema{ |
364 | | - Name: fieldName, |
365 | | - Value: fieldSchema, |
366 | | - }, |
| 479 | + namedSchema, |
367 | 480 | ) |
368 | 481 | } |
369 | 482 |
|
|
0 commit comments