Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ public static FragmentReferenceFinderResult Find(DocumentNode document, Fragment
{
var localFragmentLookup = CreateLocalFragmentLookup(document);

localFragmentLookup.Remove(fragment.Name.Value);

var context = new VisitorContext(localFragmentLookup);
s_visitor.Visit(document, context);

// The anchor is carried separately as the definition's own fragment, so it is
// excluded from the local lookup only after the document has been visited.
localFragmentLookup.Remove(fragment.Name.Value);

return new FragmentReferenceFinderResult(localFragmentLookup, context.ExternalFragmentReferences);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using HotChocolate.Language;

namespace HotChocolate.Adapters.OpenApi.Parsing;

public class FragmentReferenceFinderTests
{
[Fact]
public void Find_Should_Not_Report_Anchor_As_External_When_Sibling_Fragment_Spreads_It()
{
// arrange
var document = Utf8GraphQLParser.Parse(
"""
fragment ApiError on Error {
message
}

fragment NameTooLongError on NameTooLongError {
...ApiError
attemptedName
}
""");
var anchor = document.Definitions.OfType<FragmentDefinitionNode>().First();

// act
var result = FragmentReferenceFinder.Find(document, anchor);

// assert
Assert.Empty(result.External);
var localFragment = Assert.Single(result.Local);
Assert.Equal("NameTooLongError", localFragment.Key);
}
}
34 changes: 31 additions & 3 deletions website/content/docs/hotchocolate/build/adapters/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,15 +169,15 @@ A POST request with a JSON body `{"id": "6", "name": "Alice", "email": "alice@ex
You can define reusable GraphQL fragments as separate documents. The adapter resolves fragment references across documents:

```graphql
-- Document 1: endpoint definition
# Document 1: endpoint definition
query GetUser($userId: ID!)
@http(method: GET, route: "/users/{userId}") {
userById(id: $userId) {
...UserFields
}
}

-- Document 2: shared fragment
# Document 2: shared fragment
fragment UserFields on User {
id
name
Expand All @@ -187,14 +187,42 @@ fragment UserFields on User {
}
}

-- Document 3: another shared fragment
# Document 3: another shared fragment
fragment AddressFields on Address {
street
}
```

Each document is a separate entry in your `IOpenApiDefinitionStorage`. Fragment-only documents are treated as shared models.

## One model per document

A fragment-only document defines exactly one shared model. The **first** fragment in the document names the model, and that name is what other documents spread. Any further fragments in the same document are private helpers: fragments within that document can spread them, but no other document can reference them by name.

```graphql
# A model named "User", plus a helper only this document can spread
fragment User on User {
...UserContact
id
name
}

fragment UserContact on User {
email
address {
street
}
}
```

To make a fragment referenceable from another document, give it a document of its own.

## Fragment name uniqueness

Fragment names must be unique across every document that contributes to a single endpoint, which is the endpoint's own document plus each model it spreads, transitively. When two of those documents define a fragment with the same name, the endpoint's composed document is invalid and the endpoint responds with HTTP 500. Private helper fragments are not namespaced by their document, so this applies to them as well.

An endpoint that spreads a fragment no document defines is also invalid, and likewise responds with HTTP 500. In both cases the endpoint is still routed, and it is omitted from the OpenAPI specification.

# Storage

The `IOpenApiDefinitionStorage` interface provides endpoint and fragment definitions to the adapter:
Expand Down
Loading