Files
clubber/openspec/specs/graphql-api/spec.md
T
gurixandClaude dd772db298 chore: archive add-graphql-tools change
Archive completed change proposal to openspec/changes/archive/.
Updated specs with new GraphQL introspection and MCP tool requirements:

- graphql-api: Added GraphQL Introspection Support requirement
- mcp-integration: Added 3 requirements for GraphQL tools
  - Get GraphQL Schema Tool
  - Execute GraphQL Query Tool
  - Tool Input Schema for GraphQL Execution

All requirements include comprehensive scenarios and are now part
of the active specification.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 12:08:58 +01:00

9.8 KiB

graphql-api Specification

Purpose

TBD - created by archiving change add-graphql-member-api. Update Purpose after archive.

Requirements

Requirement: Strawberry GraphQL schema with Member type

The system MUST define a GraphQL schema using Strawberry with Member object type mapping to database model fields.

Member GraphQL type fields:

  • id: ID! (non-null unique identifier)
  • firstName: String! (non-null)
  • lastName: String (nullable)
  • street: String (nullable)
  • apartmentNumber: String (nullable)
  • zip: String (nullable)
  • city: String (nullable)
  • country: String (nullable)
  • email: String (nullable)
  • phone: String (nullable)
  • createdAt: DateTime! (non-null timestamp)
  • updatedAt: DateTime! (non-null timestamp)

Scenario: Member type is defined with Strawberry decorator

Given src/schemas/member.py exists When Member class is decorated with @strawberry.type Then GraphQL type "Member" is registered in schema And all fields use camelCase naming (GraphQL convention) And field types match SQLAlchemy model types And snake_case database fields map to camelCase GraphQL fields

Scenario: GraphQL schema is introspectable

Given FastAPI app with GraphQL endpoint is running When client accesses /graphql endpoint Then GraphiQL playground is displayed And schema introspection reveals Member type And all fields and their types are documented

Requirement: Input types for mutations

The system MUST define separate input types for create and update operations with appropriate field requirements.

CreateMemberInput fields (only firstName required):

  • firstName: String!
  • lastName: String
  • street: String
  • apartmentNumber: String
  • zip: String
  • city: String
  • country: String
  • email: String
  • phone: String

UpdateMemberInput fields (id required, all others optional):

  • id: ID!
  • firstName: String
  • lastName: String
  • street: String
  • apartmentNumber: String
  • zip: String
  • city: String
  • country: String
  • email: String
  • phone: String

Scenario: CreateMemberInput enforces required fields

Given CreateMemberInput type is defined When client sends mutation without firstName Then GraphQL validation error is returned And error message indicates firstName is required When client sends mutation with only firstName Then input validation passes And member is created with only firstName populated

Scenario: UpdateMemberInput allows partial updates

Given UpdateMemberInput type is defined When client sends mutation with only id and email Then only email field is updated in database And all other fields remain unchanged When client sends mutation without id Then GraphQL validation error is returned

Requirement: Query resolvers for reading members

The system MUST provide GraphQL query resolvers for fetching individual members and listing all members.

Query operations:

  • member(id: ID!): Member - Get single member by ID
  • members: [Member!]! - List all members

Scenario: Query single member by ID

Given member exists with id=1 When client executes query:

query {
  member(id: 1) {
    id
    firstName
    lastName
    email
  }
}

Then response contains member data And response matches GraphQL Member type structure

Scenario: Query member with non-existent ID

Given no member exists with id=999 When client executes query member(id: 999) Then response returns null for member field And no error is raised (null is valid for nullable return)

Scenario: List all members

Given multiple members exist in database When client executes query:

query {
  members {
    id
    firstName
    lastName
  }
}

Then response contains array of all members And members are sorted by last name, first name When no members exist Then response contains empty array

Requirement: Mutation resolvers for modifying members

The system MUST provide GraphQL mutation resolvers for creating, updating, and deleting members.

Mutation operations:

  • createMember(input: CreateMemberInput!): Member! - Create new member
  • updateMember(input: UpdateMemberInput!): Member! - Update existing member
  • deleteMember(id: ID!): Boolean! - Delete member

Scenario: Create new member mutation

Given valid CreateMemberInput is provided When client executes mutation:

mutation {
  createMember(input: {
    firstName: "Jane"
    lastName: "Smith"
    street: "123 Main St"
    zip: "12345"
    city: "Springfield"
    country: "USA"
    email: "jane@example.com"
    phone: "+15551234567"
  }) {
    id
    firstName
    email
  }
}

Then new member is persisted to database And response contains newly created member with generated id And createdAt and updatedAt are populated

Scenario: Create member with minimal data (firstName only)

Given CreateMemberInput with only firstName is provided When client executes mutation:

mutation {
  createMember(input: {
    firstName: "John"
  }) {
    id
    firstName
    lastName
    email
  }
}

Then new member is created in database And firstName is "John" And lastName, email, phone, and address fields are null And response contains member with null optional fields

Scenario: Create member with invalid input

Given CreateMemberInput has invalid email format When client executes createMember mutation Then GraphQL error is returned And error message indicates validation failure And no database record is created

Scenario: Update existing member mutation

Given member exists with id=1 When client executes mutation:

mutation {
  updateMember(input: {
    id: 1
    email: "newemail@example.com"
    phone: "+15559876543"
  }) {
    id
    email
    phone
    updatedAt
  }
}

Then member email and phone are updated in database And updatedAt timestamp is refreshed And all other fields remain unchanged

Scenario: Update non-existent member

Given no member exists with id=999 When client executes updateMember with id=999 Then GraphQL error is returned And error message indicates "Member not found"

Scenario: Delete member mutation

Given member exists with id=1 When client executes mutation:

mutation {
  deleteMember(id: 1)
}

Then member is removed from database And response returns true When query attempts to fetch deleted member Then response returns null

Scenario: Delete non-existent member

Given no member exists with id=999 When client executes deleteMember(id: 999) Then GraphQL error is returned And error message indicates "Member not found"

Requirement: FastAPI integration with GraphQL endpoint

The system MUST integrate Strawberry GraphQL schema with FastAPI application at /graphql path with GraphiQL playground enabled.

Scenario: GraphQL endpoint is mounted on FastAPI app

Given src/main.py creates FastAPI app When Strawberry schema is created from Query and Mutation classes Then GraphQLRouter is created with schema And router is mounted at /graphql path And GraphiQL is enabled for development

Scenario: GraphQL playground is accessible

Given FastAPI app is running When browser navigates to http://localhost:8000/graphql Then GraphiQL interface is displayed And schema documentation is available And queries can be executed interactively

Scenario: GraphQL endpoint accepts POST requests

Given GraphQL endpoint is configured When client sends POST to /graphql with query in body Then query is executed against schema And JSON response is returned with data or errors

Requirement: Error handling with meaningful messages

The system MUST provide clear, actionable error messages for validation failures, not found errors, and server errors.

Scenario: Input validation error provides field-level details

Given createMember mutation receives invalid email When mutation executes Then GraphQL error includes message "Invalid email format" And error path indicates which input field failed And HTTP status code is 400 (Bad Request)

Scenario: Not found error provides resource context

Given member query requests non-existent id=999 When query executes Then error message is "Member with ID 999 not found" And error type indicates resource not found

Scenario: Database error is handled gracefully

Given database connection fails during query When query executes Then GraphQL error indicates server error And internal error details are logged And client receives generic "Internal server error" message And HTTP status code is 500

Requirement: GraphQL Introspection Support

The GraphQL API SHALL support introspection queries that enable clients to discover the complete schema dynamically.

Scenario: Introspection query returns schema types

  • GIVEN the GraphQL API is running
  • WHEN a client executes an introspection query for __schema
  • THEN the response SHALL include all types defined in the schema
  • AND include queries, mutations, and object types
  • AND include field names, types, and descriptions

Scenario: Introspection query returns type details

  • GIVEN the GraphQL API is running
  • WHEN a client executes an introspection query for __type(name: "Member")
  • THEN the response SHALL include complete Member type details
  • AND include all fields with their types
  • AND include field descriptions where defined

Scenario: Introspection is enabled by default

  • GIVEN the Strawberry GraphQL schema is configured
  • WHEN the schema is created
  • THEN introspection SHALL be enabled
  • AND the __schema and __type queries SHALL be available