Skip to content

feat: recognize generated schema types (SchemaTypeName + UnwrapSchema)#20

Open
mlwelles wants to merge 2 commits into
matthewmcneely:mainfrom
mlwelles:feature/schema-routing
Open

feat: recognize generated schema types (SchemaTypeName + UnwrapSchema)#20
mlwelles wants to merge 2 commits into
matthewmcneely:mainfrom
mlwelles:feature/schema-routing

Conversation

@mlwelles

@mlwelles mlwelles commented Jun 4, 2026

Copy link
Copy Markdown

What this adds

A small mechanism that lets the client recognize generated schema/wrapper
types
and route them to their backing schema struct. It is the integration
point that lets a code generator (e.g. modusgraph-gen) emit wrapper types the
client understands, without the client depending on the generator.

  • Schema interface — SchemaTypeName() string; generated schema structs
    implement it.
  • UnwrapSchema(obj) — returns the schema record inside obj: identity for a
    direct Schema, the result of Unwrap() when that result is a Schema, and
    identity for everything else.
  • The 7 client mutation/query methods unwrap their argument via UnwrapSchema,
    so a wrapper routes to its inner record while plain structs pass through
    untouched.

Self-contained and additive: builds and tests green.

The problem it solves

Code generators want to hand the client a richer wrapper type (a fluent builder,
a domain object) rather than the bare schema struct. The client only knows how
to reflect over schema structs. UnwrapSchema is the bridge: a wrapper exposes
Unwrap() returning its backing Schema, and the client unwraps it before the
existing reflection pipeline runs. Plain structs implement neither Schema nor
Unwrap, so they are unaffected — UnwrapSchema is identity for them.

type Actor struct { /* ... */ }
func (a *Actor) SchemaTypeName() string { return "Actor" }

type ActorBuilder struct{ actor *Actor }
func (b *ActorBuilder) Unwrap() *Actor { return b.actor }

// Pass the wrapper straight to the client; it unwraps to the Actor.
client.Insert(ctx, &ActorBuilder{actor: &Actor{Name: "Sigourney Weaver"}})

Review round — changes since first push

Addresses the automated review (cubic):

  • Panic on typed nil pointer (P1): UnwrapSchema invoked Unwrap() on a
    typed nil pointer, which panics if the method dereferences the receiver. It
    now returns a nil pointer untouched.
  • Pointer-receiver Unwrap missed by value (P2): a wrapper passed by value
    whose Unwrap() has a pointer receiver was not unwrapped, because a value's
    method set excludes pointer-receiver methods. UnwrapSchema now looks the
    method up on an addressable copy.
  • Tests exercised a mock, not the real methods (P2): the unit tests called
    UnwrapSchema through a local recordingClient, so a real mutation method
    dropping its UnwrapSchema call would go undetected. Added an integration
    test that inserts a wrapper through the real client and reads it back.

Tests

  • Unit tests for the typed-nil-pointer and value-wrapper cases.
  • TestClientUnwrapsWrapperThroughRealMutation — inserts a wrapper via the real
    client, asserts the inner record persisted and the UID was written back.

Documentation

A runnable ExampleSchema showing the wrapper/Unwrap pattern, alongside the
existing doc comments on Schema and UnwrapSchema.

Add the Schema interface (SchemaTypeName), the UnwrapSchema reflection
helper, and the DgraphMapper interface (record.go). The client unwraps
schema-defining values at the mutation and query boundary so generated
wrapper types route to their backing schema struct. Plain structs do not
implement Schema and are unaffected — UnwrapSchema is identity for them.
@mlwelles mlwelles requested a review from matthewmcneely as a code owner June 4, 2026 17:48

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="record_test.go">

<violation number="1" location="record_test.go:73">
P2: Tests use a local `recordingClient` mock to simulate unwrapping instead of exercising real client methods, so regressions in the actual integration points (7 client mutation/query methods) may go undetected.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread record.go
Comment thread record.go
Comment thread record_test.go
seen []any
}

func (c *recordingClient) capture(obj any) any {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Tests use a local recordingClient mock to simulate unwrapping instead of exercising real client methods, so regressions in the actual integration points (7 client mutation/query methods) may go undetected.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At record_test.go, line 73:

<comment>Tests use a local `recordingClient` mock to simulate unwrapping instead of exercising real client methods, so regressions in the actual integration points (7 client mutation/query methods) may go undetected.</comment>

<file context>
@@ -0,0 +1,117 @@
+	seen []any
+}
+
+func (c *recordingClient) capture(obj any) any {
+	obj = UnwrapSchema(obj)
+	c.seen = append(c.seen, obj)
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9bc4289. Added TestClientUnwrapsWrapperThroughRealMutation, which inserts a wrapper through the real client and reads it back via Get, so a mutation method dropping its UnwrapSchema call is caught — the prior tests exercised UnwrapSchema only through a local mock. (Issue identified by cubic.)

…Schema

Addresses review feedback on generated-schema-type routing:

- UnwrapSchema no longer panics on a typed nil pointer: it returns the value
  untouched instead of invoking Unwrap on a nil receiver that may dereference.
- UnwrapSchema now finds a pointer-receiver Unwrap on a wrapper passed by
  value, by looking the method up on an addressable copy (a value's method
  set excludes pointer-receiver methods).

Tests:
- Add unit tests for the typed-nil-pointer and value-wrapper cases.
- Add an integration test (TestClientUnwrapsWrapperThroughRealMutation) that
  inserts a wrapper through the real client and reads it back, so a mutation
  method dropping its UnwrapSchema call is caught — the prior tests exercised
  UnwrapSchema in isolation through a local mock and would not catch that.

Docs: add a runnable ExampleSchema showing the wrapper/Unwrap pattern.
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.

1 participant