Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add JSONSchemaHandle() with callbacks into the reflector #136

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

Adjective-Object
Copy link

This change provides JSONSchemaHandle(ReflectorHandle), a hook in the same vein as JSONSchema() *jsonschema.Schema, but with an api object to call back into the Reflector to get rhe schema handle for a type.

type ReflectorHandle struct {
	SchemaFor     func(t any) *Schema
	SchemaForType func(t reflect.Type) *Schema
}

This allows the user to manually create complex schemas, e.g. using OneOf, AllOf, etc, while referencing other types from within their codebase, and still getting the automatic code-generation from those structs/enums.

Usage:

type TypeOne struct {
	Type     string `json:"type" jsonschema:"required,enum=one"`
	OneField string `json:"oneField"`
}
type TypeTwo struct {
	Type     string `json:"type" jsonschema:"required,enum=two"`
	TwoField string `json:"twoField"`
}
type RecursiveType struct {
	Type string         `json:"type" jsonschema:"required,enum=recursive"`
	Self *RecursiveType `json:"self"`
}

func (HandleTest) JSONSchemaHandle(handle ReflectorHandle) *Schema {
	schema := &Schema{
		OneOf: []*Schema{
			handle.SchemaFor(TypeOne{}),
			handle.SchemaForType(reflect.TypeOf(TypeTwo{})),
			handle.SchemaFor(RecursiveType{}),
		},
	}
	return schema
}

Previously, in order to reference another type in a JSONSChema() *Schema function, you would have to do one of the following:

  1. Duplicate the type's declaration of that type inline in the schema, which is cumbersome for types with reflection-generated schemas, and doesn't work well for recursive types
  2. use a manual $ref in the schema, and ensure elsewhere in your code that you at some point visit the referenced type -- this is error prone, as it requires the reflector to visit your other structs at some point
  3. create a second Reflector(), reflect on the type you want to reference, and introspect on the generated schema to extract the type definition
  • this is especially error-prone because you lose type information for other referenced types
  • you also run into infinite-recursion issues when dealing with cyclical types. This is currently avoided by using the definitions map as a visited set, but using multiple different reflectors means the new reflector will re-traverse already visited types and fall into cycles.

Names are provisional -- I'm not very good at naming and willing to accept pretty much any suggestions

Max Huang-Hobbs added 2 commits February 10, 2024 00:12
…flector

Previously, in order to reference another type in your schema, you would have to do one of the following:
1. duplicate the schema declaration of that type inline in the schema, which is cumbersome for types with inferred reflection-based schemas
2. use a manual `$ref` in the schema, and ensure elsewhere in your code that you at some point visit the referenced type
3. create a reflector(), reflect on the type you want to reference, and introspect on the generated schema to pull out the type definition
  - this is especially error-prone because you lose type information for _other_ referenced types, and you also run into infinite-recursion issues when dealing with cyclical types.

This change provides `JSONSchemaHandle(ReflectorHandle)`, a hook in the same vein as `JSONSchema() *jsonschema.Schema`, but with an api object to call back into the Reflector to get rhe schema handle for a type.

This allows the user to manually create complex schemas, e.g. using OneOf, AllOf, etc, while referencing other types from within their codebase, and still getting the automatic code-generation from those structs/enums.

```go
type ReflectorHandle struct {
	SchemaFor     func(t any) *Schema
	SchemaForType func(t reflect.Type) *Schema
}
```

Usage:
```go
type TypeOne struct {
	Type     string `json:"type" jsonschema:"required,enum=one"`
	OneField string `json:"oneField"`
}
type TypeTwo struct {
	Type     string `json:"type" jsonschema:"required,enum=two"`
	TwoField string `json:"twoField"`
}
type RecursiveType struct {
	Type string         `json:"type" jsonschema:"required,enum=recursive"`
	Self *RecursiveType `json:"self"`
}

func (HandleTest) JSONSchemaHandle(handle ReflectorHandle) *Schema {
	schema := &Schema{
		OneOf: []*Schema{
			handle.SchemaFor(TypeOne{}),
			handle.SchemaForType(reflect.TypeOf(TypeTwo{})),
			handle.SchemaFor(RecursiveType{}),
		},
	}
	return schema
}
```

Names are provisional -- I'm not very good at naming and willing to accept pretty much any suggestions
@Adjective-Object Adjective-Object changed the title Add JSONSchemaHandle() with callbacks into the reflector feat: Add JSONSchemaHandle() with callbacks into the reflector Feb 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant