Skip to content

Commit

Permalink
feat: add support for @contact directive (#21)
Browse files Browse the repository at this point in the history
Adds new `@contact` directive.

```graphql
directive @contact(
  "Contact title of the subgraph owner"
  name: String!
  "URL where the subgraph's owner can be reached"
  url: String
  "Other relevant notes can be included here; supports markdown links"
  description: String
) on SCHEMA
```

Contact schema directive can be used to provide team contact information
to your subgraph schema. This information is automatically parsed and
displayed by Apollo Studio. See [Subgraph Contact
Information](https://www.apollographql.com/docs/graphos/graphs/federated-graphs/#contact-info-for-subgraphs)
for additional details.
  • Loading branch information
dariuszkuc committed Oct 18, 2023
1 parent 98205b0 commit 97674e7
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 1 deletion.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Federation v2 directives (includes all of the v1 directives)

* `ApolloTag` applicable on schema, see [`@tag` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#tag)
* `ComposeDirective` applicable on schema, see [`@composeDirective` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#composedirective)
* `Contact` applicable on schema, see [`@contact` usage](#providing-subgraph-contact-information)
* `Inaccessible` applicable on all type definitions, see [`@inaccessible` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#inaccessible)
* `InterfaceObject` applicable on objects, see [`@interfaceObject` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#interfaceobject)
* `KeyInterface` applicable on interfaces, see [entity interface `@key` documentation](https://www.apollographql.com/docs/federation/federated-types/interfaces)
Expand Down Expand Up @@ -184,6 +185,7 @@ Federation v2 directives (includes all of the v1 directives)

* `ApolloTag` applicable on all type definitions, see [`@tag` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#tag)
* `ComposeDirective(name)` applicable on schema, see [`@composeDirective` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#composedirective)
* `Contact(name, url?, description?)` applicable on schema, see [`@contact` usage](#providing-subgraph-contact-information)
* `Inaccessible` applicable on all type definitions, see [`@inaccessible` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#inaccessible)
* `InterfaceObject` applicable on objects, see [`@interfaceObject` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#interfaceobject)
* `Key(fieldset, resolvable?)` applicable on objects, see [`@key` documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives#key)
Expand Down Expand Up @@ -294,6 +296,36 @@ public class Product
}
```

### Providing subgraph contact information

You can use the `@contact` directive to add your team's contact information to a subgraph schema. This information is displayed in Studio, which helps *other* teams know who
to contact for assistance with the subgraph. See [documentation](https://www.apollographql.com/docs/graphos/graphs/federated-graphs/#contact-info-for-subgraphs) for details.

We can apply `[Contact]` attribute on a custom schema. You then need to include `@contact` directive definition and pass your custom schema to the `AddApolloFederationV2` extension.

```csharp
[Contact("MyTeamName", "https://myteam.slack.com/archives/teams-chat-room-url", "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)")]
public class CustomSchema : FederatedSchema
{
}

var builder = WebApplication.CreateBuilder(args);

builder.Services
.AddGraphQLServer()
.AddType<ContactDirectiveType>();
.AddApolloFederationV2(new CustomSchema())
// register your types and services
;

var app = builder.Build();
app.MapGraphQL();
app.Run();
```

```csharp
```

### Migration Guide

Migrating from `HotChocolate.Federation` to `ApolloGraphQL.HotChocolate.Federation` is easy. Simply update your package import to point to a new module
Expand Down
9 changes: 9 additions & 0 deletions src/Federation/ApolloTagAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,19 @@ namespace ApolloGraphQL.HotChocolate.Federation;
public sealed class ApolloTagAttribute : Attribute
{

/// <summary>
/// Initializes new instance of <see cref="ApolloTagAttribute"/>
/// </summary>
/// <param name="name">
/// Tag metadata value
/// </param>
public ApolloTagAttribute(string name)
{
Name = name;
}

/// <summary>
/// Retrieves tag metadata value
/// </summary>
public string Name { get; }
}
1 change: 1 addition & 0 deletions src/Federation/Constants/WellKnownTypeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace ApolloGraphQL.HotChocolate.Federation.Constants;

internal static class WellKnownTypeNames
{
public const string ContactDirective = "contact";
public const string ComposeDirective = "composeDirective";
public const string Extends = "extends";
public const string External = "external";
Expand Down
76 changes: 76 additions & 0 deletions src/Federation/ContactAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using HotChocolate.Types.Descriptors;
using static ApolloGraphQL.HotChocolate.Federation.ThrowHelper;

namespace ApolloGraphQL.HotChocolate.Federation;

/// <summary>
/// <code>
/// directive @contact(
/// "Contact title of the subgraph owner"
/// name: String!
/// "URL where the subgraph's owner can be reached"
/// url: String
/// "Other relevant notes can be included here; supports markdown links"
/// description: String
/// ) on SCHEMA
/// </code>
///
/// Contact schema directive can be used to provide team contact information to your subgraph schema. This information is automatically parsed and displayed by Apollo Studio.
/// See <see href="https://www.apollographql.com/docs/graphos/graphs/federated-graphs/#contact-info-for-subgraphs">Subgraph Contact Information</see> for additional details.
///
/// NOTE: Only available in Federation v2
///
/// <example>
/// <code>
/// schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){
/// query: Query
/// }
/// </code>
/// </example>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
public sealed class ContactAttribute : SchemaTypeDescriptorAttribute
{
/// <summary>
/// Initializes new instance of <see cref="ContactAttribute"/>
/// </summary>
/// <param name="name">
/// Contact title of the subgraph owner
/// </param>
/// <param name="url">
/// URL where the subgraph's owner can be reached
/// </param>
/// <param name="description">
/// Other relevant contact notes; supports markdown links
/// </param>
public ContactAttribute(string name, string? url = null, string? description = null)
{
Name = name;
Url = url;
Description = description;
}

/// <summary>
/// Gets the contact title of the subgraph owner.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the url where the subgraph's owner can be reached.
/// </summary>
public string? Url { get; }

/// <summary>
/// Gets other relevant notes about subgraph contact information. Can include markdown links.
/// </summary>
public string? Description { get; }

public override void OnConfigure(IDescriptorContext context, ISchemaTypeDescriptor descriptor, Type type)
{
if (string.IsNullOrEmpty(Url))
{
throw Contact_Name_CannotBeEmpty(type);
}
descriptor.Contact(Name, Url, Description);
}
}
49 changes: 49 additions & 0 deletions src/Federation/Extensions/ApolloFederationDescriptorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;

using ContactDirective = ApolloGraphQL.HotChocolate.Federation.Two.Contact;
using LinkDirective = ApolloGraphQL.HotChocolate.Federation.Two.Link;

using static ApolloGraphQL.HotChocolate.Federation.Constants.WellKnownContextData;
Expand All @@ -16,6 +17,54 @@ namespace HotChocolate.Types;
/// </summary>
public static partial class ApolloFederationDescriptorExtensions
{
/// <summary>
/// Applies @contact directive which can be used to prpvode team contact information to your subgraph schema.
/// This information is automatically parsed and displayed by Apollo Studio. See
/// <see href="https://www.apollographql.com/docs/graphos/graphs/federated-graphs/#contact-info-for-subgraphs">Subgraph Contact Information</see>
/// for additional details.
///
/// <code>
/// schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){
/// query: Query
/// }
/// </code>
/// </example>

Check warning on line 31 in src/Federation/Extensions/ApolloFederationDescriptorExtensions.cs

View workflow job for this annotation

GitHub Actions / release-code

XML comment has badly formed XML -- 'End tag 'example' does not match the start tag 'summary'.'
/// </summary>

Check warning on line 32 in src/Federation/Extensions/ApolloFederationDescriptorExtensions.cs

View workflow job for this annotation

GitHub Actions / release-code

XML comment has badly formed XML -- 'End tag was not expected at this location.'
/// <param name="descriptor">
/// The object type descriptor on which this directive shall be annotated.
/// </param>
/// <param name="name">
/// Contact title of the subgraph owner
/// </param>
/// <param name="url">
/// URL where the subgraph's owner can be reached
/// </param>
/// <param name="description">
/// Other relevant contact notes; supports markdown links
/// </param>
/// <returns>
/// Returns the object type descriptor.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="descriptor"/> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> is <c>null</c>.
/// </exception>
public static ISchemaTypeDescriptor Contact(this ISchemaTypeDescriptor descriptor, string name, string? url, string? description)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}
if (name is null)
{
throw new ArgumentNullException(nameof(name));
}

return descriptor.Directive(new ContactDirective(name, url, description));
}

/// <summary>
/// Applies @composeDirective which is used to specify custom directives that should be exposed in the
/// Supergraph schema. If not specified, by default, Supergraph schema excludes all custom directives.
Expand Down
2 changes: 2 additions & 0 deletions src/Federation/MapAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ namespace ApolloGraphQL.HotChocolate.Federation;

/// <summary>
/// Maps an argument to a representation value.
///
/// WARNING: Only scalar leaf values are supported. Path cannot point to an object nor a list field.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class MapAttribute : Attribute
Expand Down
17 changes: 17 additions & 0 deletions src/Federation/Properties/FederationResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Federation/Properties/FederationResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Contact_Description" xml:space="preserve">
<value>Provides contact information of the owner responsible for this subgraph schema.</value>
</data>
<data name="ComposeDirective_Description" xml:space="preserve">
<value>Marks underlying custom directive to be included in the Supergraph schema.</value>
</data>
Expand Down Expand Up @@ -176,6 +179,9 @@
</data>
<data name="ThrowHelper_Link_Url_CannotBeEmpty" xml:space="preserve">
<value>The link attribute is used on `{0}` without specifying the url.</value>
</data>
<data name="ThrowHelper_Contact_Name_CannotBeEmpty" xml:space="preserve">
<value>The contact attribute is used on `{0}` without specifying the name.</value>
</data>
<data name="FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value>
Expand Down
14 changes: 13 additions & 1 deletion src/Federation/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ internal static class ThrowHelper
.Build());

/// <summary>
/// The link attribute is used on the type level without specifying the url.
/// The link attribute is used on the schema without specifying the url.
/// </summary>
public static SchemaException Link_Url_CannotBeEmpty(
Type type) =>
Expand All @@ -179,4 +179,16 @@ internal static class ThrowHelper
ThrowHelper_Link_Url_CannotBeEmpty,
type.FullName ?? type.Name)
.Build());

/// <summary>
/// The contact attribute is used on the schema without specifying the name.
/// </summary>
public static SchemaException Contact_Name_CannotBeEmpty(
Type type) =>
new SchemaException(
SchemaErrorBuilder.New()
.SetMessage(
ThrowHelper_Contact_Name_CannotBeEmpty,
type.FullName ?? type.Name)
.Build());
}
38 changes: 38 additions & 0 deletions src/Federation/Two/Contact.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace ApolloGraphQL.HotChocolate.Federation.Two;

public sealed class Contact
{
/// <summary>
/// Initializes new instance of <see cref="Contact"/>
/// </summary>
/// <param name="name">
/// Contact title of the subgraph owner
/// </param>
/// <param name="url">
/// URL where the subgraph's owner can be reached
/// </param>
/// <param name="description">
/// Other relevant notes can be included here; supports markdown links
/// </param>
public Contact(string name, string? url, string? description)
{
Name = name;
Url = url;
Description = description;
}

/// <summary>
/// Gets the contact title of the subgraph owner.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the url where the subgraph's owner can be reached.
/// </summary>
public string? Url { get; }

/// <summary>
/// Gets other relevant notes about subgraph contact information. Can include markdown links.
/// </summary>
public string? Description { get; }
}
38 changes: 38 additions & 0 deletions src/Federation/Two/ContactDirectiveType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using ApolloGraphQL.HotChocolate.Federation.Constants;
using ApolloGraphQL.HotChocolate.Federation.Properties;

namespace ApolloGraphQL.HotChocolate.Federation.Two;

/// <summary>
/// <code>
/// directive @contact(
/// "Contact title of the subgraph owner"
/// name: String!
/// "URL where the subgraph's owner can be reached"
/// url: String
/// "Other relevant notes can be included here; supports markdown links"
/// description: String
/// ) on SCHEMA
/// </code>
///
/// Contact schema directive can be used to provide team contact information to your subgraph schema. This information is automatically parsed and displayed by Apollo Studio.
/// See <see href="https://www.apollographql.com/docs/graphos/graphs/federated-graphs/#contact-info-for-subgraphs">Subgraph Contact Information</see> for additional details.
///
/// NOTE: Only available in Federation v2
///
/// <example>
/// <code>
/// schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){
/// query: Query
/// }
/// </code>
/// </example>
/// </summary>
public sealed class ContactDirectiveType : DirectiveType<Contact>
{
protected override void Configure(IDirectiveTypeDescriptor<Contact> descriptor)
=> descriptor
.Name(WellKnownTypeNames.ContactDirective)
.Description(FederationResources.Contact_Description)
.Location(DirectiveLocation.Schema);
}

0 comments on commit 97674e7

Please sign in to comment.