├── resources ├── custom_scalar_mapping.txt ├── icon.ico ├── icon.jpg ├── custom_scalar_mapping.json ├── configuration.json └── blog.graphql ├── MAINTAINERS.md ├── src ├── GraphQLToKarate.CommandLine │ ├── Program.cs │ ├── icon.ico │ ├── InternalsVisibleTo.cs │ ├── Settings │ │ ├── LogCommandSettings.cs │ │ ├── IConvertCommandSettingsLoader.cs │ │ ├── GraphQLToKarateUserConfiguration.cs │ │ ├── LoadedConvertCommandSettings.cs │ │ └── ConvertCommandSettingsLoader.cs │ ├── Infrastructure │ │ ├── TypeResolver.cs │ │ ├── TypeRegistrarConfigurator.cs │ │ ├── CompositionRoot.cs │ │ ├── StringToSetConverter.cs │ │ ├── CommandAppConfigurator.cs │ │ ├── LogCommandSettingsInterceptor.cs │ │ ├── TypeRegistrar.cs │ │ ├── LogLevelVerbosityConverter.cs │ │ └── HostConfigurator.cs │ ├── Exceptions │ │ └── GraphQLToKarateConfigurationException.cs │ ├── Mappers │ │ ├── IGraphQLToKarateUserConfigurationMapper.cs │ │ └── GraphQLToKarateUserConfigurationMapper.cs │ ├── Prompts │ │ └── IConvertCommandSettingsPrompt.cs │ ├── Commands │ │ └── ConvertCommand.cs │ └── GraphQLToKarate.CommandLine.csproj ├── GraphQLToKarate.Library │ ├── InternalsVisibleTo.cs │ ├── Enums │ │ └── GraphQLOperationType.cs │ ├── Tokens │ │ ├── Indent.cs │ │ ├── GraphQLToken.cs │ │ ├── KarateToken.cs │ │ └── SchemaToken.cs │ ├── Types │ │ ├── KarateTypeBase.cs │ │ ├── GraphQLArgumentTypeBase.cs │ │ ├── KarateType.cs │ │ ├── KarateListType.cs │ │ ├── KarateNullType.cs │ │ ├── KarateNonNullType.cs │ │ ├── GraphQLArgumentType.cs │ │ ├── GraphQLNonNullArgumentType.cs │ │ ├── GraphQLListArgumentType.cs │ │ ├── GraphQLOperation.cs │ │ └── KarateObject.cs │ ├── Settings │ │ ├── KarateFeatureBuilderSettings.cs │ │ └── GraphQLToKarateSettings.cs │ ├── Parsers │ │ ├── GraphQLSchemaParser.cs │ │ └── IGraphQLSchemaParser.cs │ ├── Converters │ │ ├── IGraphQLToKarateConverter.cs │ │ ├── IGraphQLInputValueDefinitionConverterFactory.cs │ │ ├── GraphQLInputValueDefinitionConverterFactory.cs │ │ ├── IGraphQLTypeConverterFactory.cs │ │ ├── IGraphQLCyclicToAcyclicConverter.cs │ │ ├── IGraphQLInputValueToExampleValueConverter.cs │ │ ├── GraphQLCustomScalarTypeConverter.cs │ │ ├── IGraphQLScalarToExampleValueConverter.cs │ │ ├── IGraphQLTypeDefinitionConverter.cs │ │ ├── GraphQLNullTypeConverter.cs │ │ ├── IGraphQLTypeConverter.cs │ │ ├── GraphQLTypeConverterFactory.cs │ │ ├── IGraphQLFieldDefinitionConverter.cs │ │ ├── GraphQLNonNullTypeConverter.cs │ │ ├── IGraphQLInputValueDefinitionConverter.cs │ │ ├── GraphQLTypeDefinitionConverter.cs │ │ ├── GraphQLListTypeConverter.cs │ │ ├── GraphQLTypeConverter.cs │ │ ├── GraphQLScalarToExampleValueConverter.cs │ │ └── GraphQLInputValueToExampleValueConverter.cs │ ├── Builders │ │ ├── IConfiguredGraphQLToKarateConverterBuilder.cs │ │ └── IGraphQLToKarateConverterBuilder.cs │ ├── Apollo │ │ └── Directives.cs │ ├── Extensions │ │ ├── HasArgumentsDefinitionNodeExtensions.cs │ │ ├── StringBuilderExtensions.cs │ │ ├── CollectionExtensions.cs │ │ ├── AdjacencyGraphExtensions.cs │ │ ├── EnumExtensions.cs │ │ ├── GraphQLDirectiveExtensions.cs │ │ ├── GraphQLFieldDefinitionExtensions.cs │ │ ├── GraphQLEnumValueDefinitionExtensions.cs │ │ ├── GraphQLInputValueDefinitionExtensions.cs │ │ ├── GraphQLTypeExtensions.cs │ │ └── StringExtensions.cs │ ├── Mappings │ │ ├── ICustomScalarMappingValidator.cs │ │ ├── ICustomScalarMappingLoader.cs │ │ ├── ICustomScalarMapping.cs │ │ ├── CustomScalarMapping.cs │ │ └── CustomScalarMappingLoader.cs │ ├── Exceptions │ │ └── InvalidGraphQLTypeException.cs │ ├── Features │ │ ├── IKarateScenarioBuilder.cs │ │ ├── IKarateFeatureBuilder.cs │ │ └── KarateFeatureBuilder.cs │ ├── GraphQLToKarate.Library.csproj │ └── Adapters │ │ └── IGraphQLDocumentAdapter.cs └── GraphQLToKarate.sln ├── renovate.json ├── tests ├── GraphQLToKarate.Integration.Api │ ├── Types │ │ ├── ISearchResult.cs │ │ ├── UserRole.cs │ │ ├── PageInfoInput.cs │ │ ├── UserConnection.cs │ │ ├── CommentConnection.cs │ │ ├── BlogPostConnection.cs │ │ ├── Node.cs │ │ ├── BlogPostExtensions.cs │ │ ├── PageInfo.cs │ │ ├── UpdateUserInput.cs │ │ ├── CreateBlogPostInput.cs │ │ ├── CreateUserInput.cs │ │ ├── Comment.cs │ │ ├── User.cs │ │ ├── BlogPost.cs │ │ └── Mutation.cs │ ├── appsettings.json │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── GraphQLToKarate.Integration.Api.csproj ├── GraphQLToKarate.Tests │ ├── Mocks │ │ └── UnsupportedGraphQLType.cs │ ├── Enums │ │ └── GraphQLOperationTypeTests.cs │ ├── Extensions │ │ ├── CollectionExtensionTests.cs │ │ ├── GraphQLDirectiveExtensionsTests.cs │ │ ├── StringExtensionsTests.cs │ │ ├── AdjacencyGraphExtensionsTests.cs │ │ ├── GraphQLInputValueDefinitionExtensionsTests.cs │ │ ├── GraphQLFieldDefinitionExtensionsTests.cs │ │ ├── GraphQLEnumValueDefinitionExtensionsTests.cs │ │ └── GraphQLTypeExtensionsTests.cs │ ├── Converters │ │ ├── GraphQLTypeConverterFactoryTests.cs │ │ └── GraphQLCustomScalarTypeConverterTests.cs │ ├── Types │ │ ├── GraphQLVariableTypeTests.cs │ │ └── KarateObjectTests.cs │ ├── GraphQLToKarate.Tests.csproj │ ├── Builders │ │ └── GraphQLToKarateConverterBuilderTests.cs │ └── Mappings │ │ └── CustomScalarMappingTest.cs └── GraphQLToKarate.CommandLine.Tests │ ├── Infrastructure │ ├── TypeRegistrarConfiguratorTests.cs │ ├── TypeRegistrarTests.cs │ ├── TypeResolverTests.cs │ ├── LogCommandSettingsInterceptorTests.cs │ ├── LogLevelVerbosityConverterTests.cs │ └── StringToSetConverterTests.cs │ ├── GraphQLToKarate.CommandLine.Tests.csproj │ ├── Mappers │ └── GraphQLToKarateUserConfigurationMapperTests.cs │ └── Settings │ └── ConvertCommandSettingsTests.cs ├── .markdownlint.json ├── stylecop.json ├── .github ├── workflows │ ├── approve.yml │ ├── stale.yml │ ├── lint.yml │ ├── integration-test.yml │ ├── test.yml │ └── codeql.yml ├── ISSUE_TEMPLATE │ ├── FEATURE_REQUEST.md │ └── BUG_REPORT.md └── PULL_REQUEST_TEMPLATE.md ├── scripts └── extract_changelog.py ├── LICENSE ├── SECURITY.md ├── CHANGELOG.md ├── .editorconfig ├── CONTRIBUTING.md └── configuration └── schema └── v1 └── schema.json /resources/custom_scalar_mapping.txt: -------------------------------------------------------------------------------- 1 | Date:string,Long:number,URL:string -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | - [@wbaldoumas](https://github.com/wbaldoumas) 4 | -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbaldoumas/graphql-to-karate/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbaldoumas/graphql-to-karate/HEAD/resources/icon.jpg -------------------------------------------------------------------------------- /resources/custom_scalar_mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "Date": "string", 3 | "Long": "number", 4 | "URL": "string" 5 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Program.cs: -------------------------------------------------------------------------------- 1 | await CompositionRoot.Build(args).RunAsync(args).ConfigureAwait(false); -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbaldoumas/graphql-to-karate/HEAD/src/GraphQLToKarate.CommandLine/icon.ico -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("GraphQLToKarate.Tests")] 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>wbaldoumas/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Enums/GraphQLOperationType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Enums; 2 | 3 | public enum GraphQLOperationType 4 | { 5 | Query, 6 | Mutation 7 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/ISearchResult.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | [UnionType("SearchResult")] 4 | public interface ISearchResult { } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/UserRole.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public enum UserRole 4 | { 5 | Guest, 6 | Standard, 7 | Administrator 8 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("GraphQLToKarate.CommandLine.Tests")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 5 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/PageInfoInput.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class PageInfoInput 4 | { 5 | public int? Limit { get; set; } 6 | 7 | public int? Offset { get; set; } 8 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/UserConnection.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class UserConnection 4 | { 5 | public required User[] Nodes { get; init; } 6 | 7 | public required PageInfo PageInfo { get; init; } 8 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/CommentConnection.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class CommentConnection 4 | { 5 | public required Comment[] Nodes { get; init; } 6 | 7 | public required PageInfo PageInfo { get; init; } 8 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/BlogPostConnection.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class BlogPostConnection 4 | { 5 | public required BlogPost[] Nodes { get; init; } 6 | 7 | public required PageInfo PageInfo { get; init; } 8 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/Node.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | [InterfaceType("Node")] 4 | public abstract class Node 5 | { 6 | [GraphQLType(typeof(IdType))] 7 | [GraphQLNonNullType] 8 | public required string Id { get; init; } 9 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/BlogPostExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | [ExtendObjectType(typeof(BlogPost))] 4 | public sealed class BlogPostExtensions 5 | { 6 | public required Uri Uri { get; set; } = new ("https://my-awesome-api.com"); 7 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/PageInfo.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class PageInfo 4 | { 5 | public bool HasNextPage { get; set; } 6 | 7 | public bool HasPreviousPage { get; set; } 8 | 9 | public int TotalCount { get; set; } 10 | } -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD013": { 4 | "line_length": 10000, 5 | "headings": false, 6 | "code_blocks": false, 7 | "tables": false 8 | }, 9 | "MD024": { 10 | "siblings_only": true 11 | }, 12 | "MD041": false, 13 | "MD034": false 14 | } 15 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Tokens/Indent.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Tokens; 2 | 3 | internal static class Indent 4 | { 5 | public const int Single = 2; 6 | 7 | public const int Double = Single * 2; 8 | 9 | public const int Triple = Single * 3; 10 | 11 | public const int Quadruple = Single * 4; 12 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/UpdateUserInput.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class UpdateUserInput 4 | { 5 | public required string Id { get; init; } 6 | 7 | public required string Name { get; init; } 8 | 9 | public required UserRole Role { get; init; } 10 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/KarateTypeBase.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Types; 2 | 3 | /// 4 | /// Represents an abstract Karate schema type. 5 | /// 6 | public abstract class KarateTypeBase 7 | { 8 | public abstract string Name { get; } 9 | 10 | public abstract string Schema { get; } 11 | } 12 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/CreateBlogPostInput.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public class CreateBlogPostInput 4 | { 5 | public required string Title { get; init; } 6 | 7 | public required string Content { get; init; } 8 | 9 | public required CreateUserInput? Author { get; init; } 10 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Mocks/UnsupportedGraphQLType.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace GraphQLToKarate.Tests.Mocks; 5 | 6 | [ExcludeFromCodeCoverage] 7 | internal sealed class UnsupportedGraphQLType : GraphQLType 8 | { 9 | public override ASTNodeKind Kind => ASTNodeKind.Alias; 10 | } -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "orderingRules": { 5 | "systemUsingDirectivesFirst": false, 6 | "usingDirectivesPlacement": "outsideNamespace" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/GraphQLArgumentTypeBase.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Types; 2 | 3 | public abstract class GraphQLArgumentTypeBase 4 | { 5 | public abstract string ArgumentName { get; } 6 | 7 | public abstract string VariableName { get; } 8 | 9 | public abstract string VariableTypeName { get; } 10 | 11 | public abstract string ExampleValue { get; } 12 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/CreateUserInput.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class CreateUserInput 4 | { 5 | public required string Name { get; init; } 6 | 7 | public required string Password { get; init; } 8 | 9 | public required UserRole Role { get; init; } 10 | 11 | public required CreateBlogPostInput BlogPost { get; init; } 12 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Settings/KarateFeatureBuilderSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.Library.Settings; 4 | 5 | [ExcludeFromCodeCoverage] 6 | public sealed class KarateFeatureBuilderSettings 7 | { 8 | public bool ExcludeQueries { get; init; } = false; 9 | 10 | public string BaseUrl { get; init; } = "\"https://www.my-awesome-api.com/graphql\""; 11 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/KarateType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Types; 2 | 3 | /// 4 | /// Represents a Karate schema type with on information about nullability, non-nullability, or list-ness. 5 | /// 6 | internal sealed class KarateType(string schema, string name) : KarateTypeBase 7 | { 8 | public override string Name { get; } = name; 9 | 10 | public override string Schema { get; } = schema; 11 | } 12 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/Comment.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class Comment : Node, ISearchResult 4 | { 5 | public required string Content { get; init; } 6 | 7 | public required User Author { get; init; } 8 | 9 | public required BlogPost BlogPost { get; init; } 10 | 11 | public required DateTime CreatedAt { get; init; } 12 | 13 | public required DateTime UpdatedAt { get; init; } 14 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/KarateListType.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Tokens; 2 | 3 | namespace GraphQLToKarate.Library.Types; 4 | 5 | /// 6 | /// Represents a list Karate schema type. 7 | /// 8 | internal sealed class KarateListType(KarateTypeBase innerType) : KarateTypeBase 9 | { 10 | public override string Name => innerType.Name; 11 | 12 | public override string Schema => $"{KarateToken.Array} {innerType.Schema}"; 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/KarateNullType.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Tokens; 2 | 3 | namespace GraphQLToKarate.Library.Types; 4 | 5 | /// 6 | /// Represents a nullable Karate schema type. 7 | /// 8 | internal sealed class KarateNullType(KarateTypeBase innerType) : KarateTypeBase 9 | { 10 | public override string Name => innerType.Name; 11 | 12 | public override string Schema => $"{KarateToken.Null}{innerType.Schema}"; 13 | } 14 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/User.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class User : Node, ISearchResult 4 | { 5 | public required string Name { get; init; } 6 | 7 | public required UserRole Role { get; init; } 8 | 9 | public required IEnumerable BlogPosts { get; init; } 10 | 11 | public required DateTime CreatedAt { get; init; } 12 | 13 | public required DateTime UpdatedAt { get; init; } 14 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/KarateNonNullType.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Tokens; 2 | 3 | namespace GraphQLToKarate.Library.Types; 4 | 5 | /// 6 | /// Represents a non-nullable Karate schema type. 7 | /// 8 | internal sealed class KarateNonNullType(KarateTypeBase innerType) : KarateTypeBase 9 | { 10 | public override string Name => innerType.Name; 11 | 12 | public override string Schema => $"{KarateToken.NonNull}{innerType.Schema}"; 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Parsers/GraphQLSchemaParser.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser; 2 | using GraphQLParser.AST; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace GraphQLToKarate.Library.Parsers; 6 | 7 | /// 8 | [ExcludeFromCodeCoverage(Justification = "Just a wrapper to enable dependency injection.")] 9 | public sealed class GraphQLSchemaParser : IGraphQLSchemaParser 10 | { 11 | public GraphQLDocument Parse(string source) => Parser.Parse(source); 12 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Settings/LogCommandSettings.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Spectre.Console.Cli; 3 | using System.ComponentModel; 4 | 5 | namespace GraphQLToKarate.CommandLine.Settings; 6 | 7 | internal class LogCommandSettings : CommandSettings 8 | { 9 | [CommandOption("--log-level")] 10 | [Description("Minimum level for logging")] 11 | [TypeConverter(typeof(LogLevelVerbosityConverter))] 12 | [DefaultValue(LogEventLevel.Information)] 13 | public LogEventLevel LogLevel { get; set; } 14 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/BlogPost.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class BlogPost : Node, ISearchResult 4 | { 5 | public required string Title { get; init; } 6 | 7 | public required string Content { get; init; } 8 | 9 | public required User Author { get; init; } 10 | 11 | public required IEnumerable Comments { get; init; } 12 | 13 | public required DateTime CreatedAt { get; init; } 14 | 15 | public required DateTime UpdatedAt { get; init; } 16 | } -------------------------------------------------------------------------------- /.github/workflows/approve.yml: -------------------------------------------------------------------------------- 1 | name: Auto-Approve Renovate 2 | on: 3 | pull_request: 4 | branches: [ main ] 5 | 6 | permissions: 7 | pull-requests: write 8 | 9 | jobs: 10 | approve: 11 | runs-on: ubuntu-latest 12 | 13 | if: ${{ github.actor == 'renovate[bot]' }} 14 | steps: 15 | - name: ✅ Approve Renovate Pull Request 16 | run: gh pr review --approve "$PR_URL" 17 | env: 18 | PR_URL: ${{ github.event.pull_request.html_url }} 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLToKarateConverter.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Converters; 2 | 3 | /// 4 | /// Converts GraphQL schemas to Karate features. 5 | /// 6 | public interface IGraphQLToKarateConverter 7 | { 8 | /// 9 | /// Converts the given GraphQL to a Karate feature. 10 | /// 11 | /// The source GraphQL schema to convert. 12 | /// The converted Karate feature. 13 | string Convert(string schema); 14 | } -------------------------------------------------------------------------------- /resources/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/wbaldoumas/graphql-to-karate/main/configuration/schema/v1/schema.json", 3 | "outputFile": "blog.feature", 4 | "baseUrl": "\"http://localhost:9001\"", 5 | "excludeQueries": false, 6 | "includeMutations": true, 7 | "queryName": "Query", 8 | "mutationName": "Mutation", 9 | "typeFilter": [], 10 | "queryOperationFilter": [], 11 | "mutationOperationFilter": [], 12 | "customScalarMapping": { 13 | "DateTime": "string", 14 | "URL": "string" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/TypeResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Spectre.Console.Cli; 3 | 4 | namespace GraphQLToKarate.CommandLine.Infrastructure; 5 | 6 | /// 7 | internal sealed class TypeResolver(IHost? host) : ITypeResolver, IDisposable 8 | { 9 | private readonly IHost _host = host ?? throw new ArgumentNullException(nameof(host)); 10 | 11 | public object? Resolve(Type? serviceType) => serviceType is not null ? _host.Services.GetService(serviceType) : null; 12 | 13 | public void Dispose() => _host.Dispose(); 14 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/GraphQLArgumentType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Types; 2 | 3 | public sealed class GraphQLArgumentType( 4 | string argumentName, 5 | string variableName, 6 | string variableTypeName, 7 | string exampleValue) 8 | : GraphQLArgumentTypeBase 9 | { 10 | public override string ArgumentName { get; } = argumentName; 11 | 12 | public override string VariableName { get; } = variableName; 13 | 14 | public override string VariableTypeName { get; } = variableTypeName; 15 | 16 | public override string ExampleValue { get; } = exampleValue; 17 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/GraphQLNonNullArgumentType.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Tokens; 2 | 3 | namespace GraphQLToKarate.Library.Types; 4 | 5 | internal sealed class GraphQLNonNullArgumentType(GraphQLArgumentTypeBase innerType) : GraphQLArgumentTypeBase 6 | { 7 | public override string ArgumentName => innerType.ArgumentName; 8 | 9 | public override string VariableName => innerType.VariableName; 10 | 11 | public override string VariableTypeName => $"{innerType.VariableTypeName}{GraphQLToken.NonNull}"; 12 | 13 | public override string ExampleValue => innerType.ExampleValue; 14 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Builders/IConfiguredGraphQLToKarateConverterBuilder.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Converters; 2 | 3 | namespace GraphQLToKarate.Library.Builders; 4 | 5 | /// 6 | /// A fully configured . 7 | /// 8 | public interface IConfiguredGraphQLToKarateConverterBuilder 9 | { 10 | /// 11 | /// Build the configured . 12 | /// 13 | /// The configured . 14 | IGraphQLToKarateConverter Build(); 15 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLInputValueDefinitionConverterFactory.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Converters; 2 | 3 | /// 4 | /// A factory for creating instances of . 5 | /// 6 | public interface IGraphQLInputValueDefinitionConverterFactory 7 | { 8 | /// 9 | /// Creates a new . 10 | /// 11 | /// The newly created . 12 | IGraphQLInputValueDefinitionConverter Create(); 13 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/GraphQLListArgumentType.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Tokens; 2 | 3 | namespace GraphQLToKarate.Library.Types; 4 | 5 | internal sealed class GraphQLListArgumentType(GraphQLArgumentTypeBase innerType) : GraphQLArgumentTypeBase 6 | { 7 | public override string ArgumentName => innerType.ArgumentName; 8 | 9 | public override string VariableName => innerType.VariableName; 10 | 11 | public override string VariableTypeName => $"{SchemaToken.OpenBracket}{innerType.VariableTypeName}{SchemaToken.CloseBracket}"; 12 | 13 | public override string ExampleValue => innerType.ExampleValue; 14 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLInputValueDefinitionConverterFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.Library.Converters; 4 | 5 | /// 6 | [ExcludeFromCodeCoverage] 7 | public sealed class GraphQLInputValueDefinitionConverterFactory(IGraphQLInputValueToExampleValueConverter graphQLInputValueToExampleValue) : IGraphQLInputValueDefinitionConverterFactory 8 | { 9 | public IGraphQLInputValueDefinitionConverter Create() => new GraphQLInputValueDefinitionConverter( 10 | graphQLInputValueToExampleValue 11 | ); 12 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Apollo/Directives.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.Library.Apollo; 4 | 5 | /// 6 | /// Directives used by the Apollo Federation specification. Currently, the only ones that 7 | /// matter within the context of this library are and , 8 | /// since they should be ignored when generating Karate tests. 9 | /// 10 | [ExcludeFromCodeCoverage] 11 | internal static class Directives 12 | { 13 | public const string External = "external"; 14 | public const string Inaccessible = "inaccessible"; 15 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Parsers/IGraphQLSchemaParser.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace GraphQLToKarate.Library.Parsers; 4 | 5 | /// 6 | /// Parses a GraphQL schema source string into a . 7 | /// 8 | public interface IGraphQLSchemaParser 9 | { 10 | /// 11 | /// Generates an AST based on the schema. 12 | /// 13 | /// Input data as a sequence of characters. 14 | /// An AST (Abstract Syntax Tree) for GraphQL document. 15 | GraphQLDocument Parse(string source); 16 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/HasArgumentsDefinitionNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace GraphQLToKarate.Library.Extensions; 4 | 5 | internal static class HasArgumentsDefinitionNodeExtensions 6 | { 7 | /// 8 | /// Returns whether the has arguments or not. 9 | /// 10 | /// The source node to check. 11 | /// Whether the actually has arguments. 12 | public static bool HasArguments(this IHasArgumentsDefinitionNode source) => source.Arguments?.Any() ?? false; 13 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/StringBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace GraphQLToKarate.Library.Extensions; 4 | 5 | internal static class StringBuilderExtensions 6 | { 7 | /// 8 | /// Trims the last characters from the end of the . 9 | /// 10 | /// The to manipulate. 11 | /// The count of characters to trim from the end of the . 12 | public static void TrimEnd(this StringBuilder source, int count) => source.Remove(source.Length - count, count); 13 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLTypeConverterFactory.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace GraphQLToKarate.Library.Converters; 4 | 5 | /// 6 | /// Used to create various implementations. 7 | /// 8 | public interface IGraphQLTypeConverterFactory 9 | { 10 | IGraphQLTypeConverter CreateGraphQLTypeConverter(); 11 | 12 | IGraphQLTypeConverter CreateGraphQLListTypeConverter(); 13 | 14 | IGraphQLTypeConverter CreateGraphQLNonNullTypeConverter(); 15 | 16 | IGraphQLTypeConverter CreateGraphQLNullTypeConverter(); 17 | 18 | IGraphQLTypeConverter CreateGraphQLTypeConverter(GraphQLType graphQLType); 19 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a feature for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Problem Statement 11 | 12 | Please describe the problem to be addressed by the proposed feature. 13 | 14 | ## Proposed Solution 15 | 16 | Please describe what you envision the solution to this problem would look like. 17 | 18 | ## Alternatives Considered 19 | 20 | Please briefly describe which alternatives, if any, have been considered, including merits of alternate approaches and 21 | tradeoffs being made. 22 | 23 | ## Additional Context 24 | 25 | Please provide any other information that may be relevant. 26 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Builders/IGraphQLToKarateConverterBuilder.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Converters; 2 | 3 | namespace GraphQLToKarate.Library.Builders; 4 | 5 | /// 6 | /// A builder which can create a configured . 7 | /// 8 | public interface IGraphQLToKarateConverterBuilder 9 | { 10 | /// 11 | /// Begin configuring a new . 12 | /// 13 | /// A to configure the with. 14 | IConfigurableGraphQLToKarateConverterBuilder Configure(); 15 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Tokens/GraphQLToken.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.Library.Tokens; 4 | 5 | /// 6 | /// Contains tokens for each of the available GraphQL types. 7 | /// 8 | [ExcludeFromCodeCoverage] 9 | public static class GraphQLToken 10 | { 11 | public const string Id = "ID"; 12 | 13 | public const string String = "String"; 14 | 15 | public const string Int = "Int"; 16 | 17 | public const string Float = "Float"; 18 | 19 | public const string Boolean = "Boolean"; 20 | 21 | public const string Query = "Query"; 22 | 23 | public const string Mutation = "Mutation"; 24 | 25 | public const char NonNull = '!'; 26 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Exceptions/GraphQLToKarateConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.CommandLine.Exceptions; 4 | 5 | [Serializable] 6 | [ExcludeFromCodeCoverage] 7 | internal sealed class GraphQLToKarateConfigurationException : Exception 8 | { 9 | public const string DefaultMessage = "Failed to load configuration from file!"; 10 | 11 | public GraphQLToKarateConfigurationException() { } 12 | 13 | public GraphQLToKarateConfigurationException(string? message) 14 | : base(message) { } 15 | 16 | public GraphQLToKarateConfigurationException(string? message, Exception? innerException) 17 | : base(message, innerException) { } 18 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Tokens/KarateToken.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.Library.Tokens; 4 | 5 | /// 6 | /// Contains tokens for each of the available Karate types. 7 | /// 8 | [ExcludeFromCodeCoverage] 9 | internal static class KarateToken 10 | { 11 | public const string String = "string"; 12 | 13 | public const string Number = "number"; 14 | 15 | public const string Boolean = "boolean"; 16 | 17 | public const string Object = "object"; 18 | 19 | public const string Present = "present"; 20 | 21 | public const string Null = "##"; 22 | 23 | public const string NonNull = "#"; 24 | 25 | public const string Array = "[]"; 26 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Settings/IConvertCommandSettingsLoader.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.CommandLine.Settings; 2 | 3 | /// 4 | /// Loads GraphQL-to-Karate data from a given . 5 | /// 6 | internal interface IConvertCommandSettingsLoader 7 | { 8 | /// 9 | /// Load the GraphQL-to-Karate data to convert from the given . 10 | /// 11 | /// The settings to load data into the application with. 12 | /// The loaded settings data. 13 | Task LoadAsync(ConvertCommandSettings convertCommandSettings); 14 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Mappings/ICustomScalarMappingValidator.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Mappings; 2 | 3 | /// 4 | /// Can validate whether a custom scalar mapping is loadable from a file or text. 5 | /// 6 | public interface ICustomScalarMappingValidator 7 | { 8 | /// 9 | /// Returns whether the custom scalar mapping source (file path or raw text) is valid. 10 | /// 11 | /// The custom scalar mapping source (file path or raw text) to check. 12 | /// Whether or not the custom scalar mapping source (file path or raw text) is valid. 13 | bool IsValid(string? customScalarMappingSource); 14 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Integration.Api.Types; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | builder.Services 6 | .AddGraphQLServer() 7 | .AddType() 8 | .AddQueryType() 9 | .AddMutationType() 10 | .AddType() 11 | .AddType() 12 | .AddType() 13 | .AddType() 14 | .AddType() 15 | .AddType() 16 | .AddType() 17 | .AddType() 18 | .AddType() 19 | .AddTypeExtension(); 20 | 21 | var app = builder.Build(); 22 | 23 | app.UseHttpsRedirection(); 24 | 25 | app.MapGraphQL(); 26 | 27 | app.Run(); -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/TypeRegistrarConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Spectre.Console.Cli; 2 | 3 | namespace GraphQLToKarate.CommandLine.Infrastructure; 4 | 5 | /// 6 | /// Configures the type registrar with the configured host builder. 7 | /// 8 | internal static class TypeRegistrarConfigurator 9 | { 10 | /// 11 | /// Configures the type registrar with the configured host builder. 12 | /// 13 | /// The command line arguments. 14 | /// The configured type registrar. 15 | public static ITypeRegistrar ConfigureTypeRegistrar(string[]? args) => new TypeRegistrar( 16 | HostConfigurator.ConfigureHostBuilder(args) 17 | ); 18 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Tokens/SchemaToken.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Tokens; 2 | 3 | /// 4 | /// Tokens for use with schema generation. 5 | /// 6 | internal static class SchemaToken 7 | { 8 | public const char OpenBrace = '{'; 9 | 10 | public const char CloseBrace = '}'; 11 | 12 | public const char Space = ' '; 13 | 14 | public const char Comma = ','; 15 | 16 | public const string Indent = " "; 17 | 18 | public const char OpenBracket = '['; 19 | 20 | public const char CloseBracket = ']'; 21 | 22 | public const char OpenParen = '('; 23 | 24 | public const char CloseParen = ')'; 25 | 26 | public const string TripleQuote = "\"\"\""; 27 | 28 | public const char Colon = ':'; 29 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Exceptions/InvalidGraphQLTypeException.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.Library.Exceptions; 4 | 5 | /// 6 | /// An exception to be thrown when an invalid GraphQL type is encountered. 7 | /// 8 | [ExcludeFromCodeCoverage(Justification = $"This is just a simple exception inheriting from the base {nameof(Exception)} class.")] 9 | public sealed class InvalidGraphQLTypeException : Exception 10 | { 11 | public InvalidGraphQLTypeException() { } 12 | 13 | public InvalidGraphQLTypeException(string? message) 14 | : base(message) { } 15 | 16 | public InvalidGraphQLTypeException(string? message, Exception? innerException) 17 | : base(message, innerException) { } 18 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Mappings/ICustomScalarMappingLoader.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Mappings; 2 | 3 | /// 4 | /// A class which can load custom scalar mappings, mapping custom scalar 5 | /// GraphQL types to their Karate equivalents. 6 | /// 7 | public interface ICustomScalarMappingLoader : ICustomScalarMappingValidator 8 | { 9 | /// 10 | /// Load the custom scalar mapping from the given source (file path or raw text). 11 | /// 12 | /// The custom scalar mapping source (file path or raw text) to load the custom scalar mapping from. 13 | /// The custom scalar mapping. 14 | Task LoadAsync(string? customScalarMappingSource); 15 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | Please provide a description of the problem. 13 | 14 | ## Expected Behaviour 15 | 16 | Please describe what you expected would happen. 17 | 18 | ## Actual Behaviour 19 | 20 | Please describe what happened instead. 21 | 22 | ## Affected Version 23 | 24 | Please provide the version number where this issue was encountered. 25 | 26 | ## Steps to Reproduce 27 | 28 | 1. First step 29 | 1. Second step 30 | 1. etc. 31 | 32 | ## Checklist 33 | 34 | - [ ] I have read the [contributing guidelines](https://github.com/wbaldoumas/graphql-to-karate/blob/main/CONTRIBUTING.md) 35 | - [ ] I have verified this does not duplicate an existing issue 36 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "launchBrowser": true, 7 | "launchUrl": "graphql", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | }, 11 | "dotnetRunMessages": true, 12 | "applicationUrl": "http://localhost:9001" 13 | }, 14 | "https": { 15 | "commandName": "Project", 16 | "launchBrowser": true, 17 | "launchUrl": "graphql", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | }, 21 | "dotnetRunMessages": true, 22 | "applicationUrl": "https://localhost:7134;http://localhost:9001" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /scripts/extract_changelog.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def main(changelog_file, output_file): 4 | with open(changelog_file, "r") as file: 5 | content = file.read() 6 | 7 | # Split the content by release sections 8 | sections = content.split("\n## ") 9 | latest_release = sections[1].strip() 10 | 11 | # Add the correct header values to the first line 12 | first_line, rest = latest_release.split("\n", 1) 13 | formatted_first_line = f"## {first_line}\n" 14 | 15 | # Combine the formatted first line and the rest of the release notes 16 | formatted_latest_release = formatted_first_line + rest 17 | 18 | # Write the latest release to a markdown file 19 | with open(output_file, "w") as file: 20 | file.write(formatted_latest_release) 21 | 22 | if __name__ == "__main__": 23 | main(sys.argv[1], sys.argv[2]) 24 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Features/IKarateScenarioBuilder.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Adapters; 2 | using GraphQLToKarate.Library.Types; 3 | 4 | namespace GraphQLToKarate.Library.Features; 5 | 6 | /// 7 | /// Builds a Karate API testing scenario in string format. 8 | /// 9 | public interface IKarateScenarioBuilder 10 | { 11 | /// 12 | /// Build a Karate API testing scenario in string format. 13 | /// 14 | /// The GraphQL query field type to build a Karate scenario for. 15 | /// The GraphQL document adapter to use. 16 | /// A Karate API testing scenario in string format. 17 | string Build(GraphQLOperation graphQLOperation, IGraphQLDocumentAdapter graphQLDocumentAdapter); 18 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/CompositionRoot.cs: -------------------------------------------------------------------------------- 1 | global using GraphQLToKarate.CommandLine.Infrastructure; 2 | using Spectre.Console.Cli; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace GraphQLToKarate.CommandLine.Infrastructure; 6 | 7 | /// 8 | /// Configures the command app. 9 | /// 10 | [ExcludeFromCodeCoverage(Justification = "Just abstraction and orchestration of app bootstrapping...")] 11 | internal static class CompositionRoot 12 | { 13 | /// 14 | /// Builds the command app. 15 | /// 16 | /// The command line arguments. 17 | /// The command app. 18 | public static CommandApp Build(string[]? args) => CommandAppConfigurator.ConfigureCommandApp( 19 | TypeRegistrarConfigurator.ConfigureTypeRegistrar(args) 20 | ); 21 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/Types/Mutation.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Integration.Api.Types; 2 | 3 | public sealed class Mutation 4 | { 5 | public User CreateUser(CreateUserInput input) => new() 6 | { 7 | Id = Guid.NewGuid().ToString(), 8 | Name = input.Name, 9 | Role = input.Role, 10 | BlogPosts = new List(), 11 | CreatedAt = DateTime.UtcNow, 12 | UpdatedAt = DateTime.UtcNow, 13 | }; 14 | 15 | public User UpdateUser(UpdateUserInput input) => new() 16 | { 17 | Id = input.Id, 18 | Name = input.Name, 19 | Role = input.Role, 20 | BlogPosts = new List(), 21 | CreatedAt = DateTime.UtcNow, 22 | UpdatedAt = DateTime.UtcNow, 23 | }; 24 | 25 | public bool DeleteUser([GraphQLType(typeof(IdType))][GraphQLNonNullType] string id) => true; 26 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Mappers/IGraphQLToKarateUserConfigurationMapper.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Settings; 2 | 3 | namespace GraphQLToKarate.CommandLine.Mappers; 4 | 5 | /// 6 | /// A mapper that maps a to a . 7 | /// 8 | internal interface IGraphQLToKarateUserConfigurationMapper 9 | { 10 | /// 11 | /// Maps a to a . 12 | /// 13 | /// The to map. 14 | /// A . 15 | LoadedConvertCommandSettings Map(GraphQLToKarateUserConfiguration graphQLToKarateUserConfiguration); 16 | } -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Identify and close stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * 0" 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | 14 | steps: 15 | - uses: actions/stale@v10 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | days-before-stale: 30 19 | stale-issue-label: 'stale' 20 | stale-pr-label: 'stale' 21 | stale-issue-message: 'Automatically marking issue as stale due to lack of activity' 22 | stale-pr-message: 'Automatically marking pull request as stale due to lack of activity' 23 | days-before-close: 10 24 | close-issue-message: 'Automatically closing this issue as stale' 25 | close-pr-message: 'Automatically closing this pull request as stale' 26 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLCyclicToAcyclicConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | 4 | namespace GraphQLToKarate.Library.Converters; 5 | 6 | /// 7 | /// Removes cycles from GraphQL field definitions. 8 | /// 9 | public interface IGraphQLCyclicToAcyclicConverter 10 | { 11 | /// 12 | /// Removes cycles from the given . 13 | /// 14 | /// The field definition to remove cycles from. 15 | /// The GraphQL document adapter, providing access to user-defined types within the GraphQL document. 16 | void Convert( 17 | GraphQLFieldDefinition graphQLFieldDefinition, 18 | IGraphQLDocumentAdapter graphQLDocumentAdapter 19 | ); 20 | } -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | markdown: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | - name: 🧼 lint markdown files 15 | uses: avto-dev/markdown-lint@v1 16 | with: 17 | config: ".markdownlint.json" 18 | args: "**/*.md .github/**/*.md" 19 | 20 | json: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v6 24 | - name: 🧼 lint json files 25 | uses: ocular-d/json-linter@0.0.2 26 | 27 | renovate: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v6 31 | - name: 🧼 lint renovate config 32 | uses: suzuki-shunsuke/github-action-renovate-config-validator@v1.1.1 33 | with: 34 | config_file_path: "renovate.json" 35 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Settings/GraphQLToKarateSettings.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Tokens; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace GraphQLToKarate.Library.Settings; 5 | 6 | [ExcludeFromCodeCoverage] 7 | public sealed class GraphQLToKarateSettings 8 | { 9 | public bool ExcludeQueries { get; init; } = false; 10 | 11 | public bool IncludeMutations { get; init; } = false; 12 | 13 | public string QueryName { get; init; } = GraphQLToken.Query; 14 | 15 | public string MutationName { get; init; } = GraphQLToken.Mutation; 16 | 17 | public ISet TypeFilter { get; init; } = new HashSet(StringComparer.OrdinalIgnoreCase); 18 | 19 | public ISet QueryOperationFilter { get; init; } = new HashSet(StringComparer.OrdinalIgnoreCase); 20 | 21 | public ISet MutationOperationFilter { get; init; } = new HashSet(StringComparer.OrdinalIgnoreCase); 22 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/StringToSetConverter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Globalization; 3 | 4 | namespace GraphQLToKarate.CommandLine.Infrastructure; 5 | 6 | /// 7 | /// Converts a comma-separated string into an ISet of strings, trimming whitespace. 8 | /// 9 | internal sealed class StringToSetConverter : TypeConverter 10 | { 11 | public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) 12 | { 13 | if (value is not string stringValue) 14 | { 15 | throw new NotSupportedException("Can't convert type filter value to type filter."); 16 | } 17 | 18 | return stringValue 19 | .Split(',') 20 | .Select(item => item.Trim()) 21 | .Where(item => !string.IsNullOrEmpty(item)) 22 | .ToHashSet(StringComparer.OrdinalIgnoreCase); 23 | } 24 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Prompts/IConvertCommandSettingsPrompt.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Settings; 2 | 3 | namespace GraphQLToKarate.CommandLine.Prompts; 4 | 5 | /// 6 | /// Prompt the user for settings in an interactive way. 7 | /// 8 | internal interface IConvertCommandSettingsPrompt 9 | { 10 | /// 11 | /// Prompt the user for settings in an interactive way. 12 | /// 13 | /// 14 | /// The initial settings to use as a starting point. This lets the user pass some initial options in the 15 | /// up-front command, which are then used as defaults when they are prompted interactively. 16 | /// 17 | /// The settings that the user has chosen. 18 | Task PromptAsync(LoadedConvertCommandSettings initialLoadedConvertCommandSettings); 19 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/GraphQLOperation.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Enums; 3 | using GraphQLToKarate.Library.Extensions; 4 | 5 | namespace GraphQLToKarate.Library.Types; 6 | 7 | public sealed class GraphQLOperation(GraphQLFieldDefinition graphQLFieldDefinition) 8 | { 9 | public string Name => graphQLFieldDefinition.NameValue(); 10 | 11 | public string OperationName => $"{Name.FirstCharToUpper()}Test"; 12 | 13 | public GraphQLType ReturnType => graphQLFieldDefinition.Type; 14 | 15 | public string ReturnTypeName => graphQLFieldDefinition.Type.GetUnwrappedTypeName(); 16 | 17 | public bool IsListReturnType => graphQLFieldDefinition.Type.IsListType(); 18 | 19 | public required string OperationString { get; init; } 20 | 21 | public required GraphQLOperationType Type { get; init; } 22 | 23 | public required ICollection Arguments { get; init; } 24 | } 25 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Infrastructure/TypeRegistrarConfiguratorTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Infrastructure; 3 | using GraphQLToKarate.CommandLine.Settings; 4 | using NUnit.Framework; 5 | 6 | namespace GraphQLToKarate.CommandLine.Tests.Infrastructure; 7 | 8 | [TestFixture] 9 | internal sealed class TypeRegistrarConfiguratorTests 10 | { 11 | [Test] 12 | public void TypeRegistrarConfigurator_configures_expected_TypeRegistrar() 13 | { 14 | // arrange + act 15 | var typeRegistrar = TypeRegistrarConfigurator.ConfigureTypeRegistrar(Array.Empty()); 16 | var typeResolver = typeRegistrar.Build(); 17 | 18 | // assert 19 | typeResolver 20 | .Should() 21 | .NotBeNull(); 22 | 23 | typeResolver 24 | .Resolve(typeof(ConvertCommandSettings)) 25 | .Should() 26 | .BeOfType(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Features/IKarateFeatureBuilder.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Adapters; 2 | using GraphQLToKarate.Library.Types; 3 | 4 | namespace GraphQLToKarate.Library.Features; 5 | 6 | /// 7 | /// Builds a Karate API testing feature in string format. 8 | /// 9 | public interface IKarateFeatureBuilder 10 | { 11 | /// 12 | /// Builds a Karate API testing feature in string format. 13 | /// 14 | /// The Karate schema objects. 15 | /// The GraphQL query objects. 16 | /// The GraphQL document adapter to use. 17 | /// The Karate feature as a string. 18 | string Build( 19 | IEnumerable karateObjects, 20 | IEnumerable graphQLOperations, 21 | IGraphQLDocumentAdapter graphQLDocumentAdapter 22 | ); 23 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Enums/GraphQLOperationTypeTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Enums; 3 | using GraphQLToKarate.Library.Extensions; 4 | using NUnit.Framework; 5 | using System.ComponentModel; 6 | 7 | namespace GraphQLToKarate.Tests.Enums; 8 | 9 | [TestFixture] 10 | internal sealed class GraphQLOperationTypeTests 11 | { 12 | [Test] 13 | [TestCase(GraphQLOperationType.Query, "query")] 14 | [TestCase(GraphQLOperationType.Mutation, "mutation")] 15 | public void Name_returns_expected_value( 16 | GraphQLOperationType graphQLOperationType, 17 | string expectedValue 18 | ) => graphQLOperationType.Name().Should().Be(expectedValue); 19 | 20 | [Test] 21 | public void Name_throws_exception_for_invalid_value() 22 | { 23 | // arrange 24 | var act = () => ((GraphQLOperationType)5).Name(); 25 | 26 | // assert 27 | act.Should().Throw(); 28 | } 29 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLInputValueToExampleValueConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | 4 | namespace GraphQLToKarate.Library.Converters; 5 | 6 | /// 7 | /// Converts a to an example value. 8 | /// 9 | public interface IGraphQLInputValueToExampleValueConverter 10 | { 11 | /// 12 | /// Converts a to an example value. 13 | /// 14 | /// The to convert. 15 | /// The to use. 16 | /// The converted example value. 17 | string Convert( 18 | GraphQLInputValueDefinition graphQLInputValueDefinition, 19 | IGraphQLDocumentAdapter graphQLDocumentAdapter 20 | ); 21 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLCustomScalarTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Extensions; 4 | using GraphQLToKarate.Library.Mappings; 5 | using GraphQLToKarate.Library.Types; 6 | 7 | namespace GraphQLToKarate.Library.Converters; 8 | 9 | /// 10 | public sealed class GraphQLCustomScalarTypeConverter( 11 | ICustomScalarMapping customScalarMapping, 12 | IGraphQLTypeConverter graphQLTypeConverter) : IGraphQLTypeConverter 13 | { 14 | public KarateTypeBase Convert( 15 | string graphQLFieldName, 16 | GraphQLType graphQLType, 17 | IGraphQLDocumentAdapter graphQLDocumentAdapter 18 | ) => customScalarMapping.TryGetKarateType(graphQLType.GetUnwrappedTypeName(), out var karateType) 19 | ? new KarateType(karateType, graphQLFieldName) 20 | : graphQLTypeConverter.Convert(graphQLFieldName, graphQLType, graphQLDocumentAdapter); 21 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Extensions; 2 | 3 | /// 4 | /// A home for extension methods on . 5 | /// 6 | internal static class CollectionExtensions 7 | { 8 | /// 9 | /// Returns true if the collection is empty or contains the given item. This method is useful 10 | /// for optional "filter" type operations, where collection to filter with would be empty if 11 | /// the user didn't specify any filters. 12 | /// 13 | /// The type of the collection. 14 | /// The collection to check. 15 | /// The item to check for. 16 | /// Whether the collection is empty or contains the given item. 17 | public static bool NoneOrContains(this ICollection collection, T item) => 18 | !collection.Any() || collection.Contains(item); 19 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/CommandAppConfigurator.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Commands; 2 | using Spectre.Console.Cli; 3 | 4 | namespace GraphQLToKarate.CommandLine.Infrastructure; 5 | 6 | /// 7 | /// Configures the command app. 8 | /// 9 | internal static class CommandAppConfigurator 10 | { 11 | /// 12 | /// Configures the command app. 13 | /// 14 | /// The type registrar to use with the command app. 15 | /// The configured command app. 16 | public static CommandApp ConfigureCommandApp(ITypeRegistrar typeRegistrar) 17 | { 18 | var commandApp = new CommandApp(typeRegistrar); 19 | 20 | commandApp.Configure(configurator => 21 | { 22 | configurator.SetInterceptor(new LogCommandSettingsInterceptor()); 23 | configurator.AddCommand("convert"); 24 | }); 25 | 26 | return commandApp; 27 | } 28 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/AdjacencyGraphExtensions.cs: -------------------------------------------------------------------------------- 1 | using QuikGraph; 2 | using QuikGraph.Algorithms; 3 | 4 | namespace GraphQLToKarate.Library.Extensions; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | internal static class AdjacencyGraphExtensions 10 | { 11 | /// 12 | /// Determines whether the given is cyclic. 13 | /// 14 | /// The vertex type. 15 | /// The edge type. 16 | /// The to check. 17 | /// true if the given is cyclic; otherwise, false. 18 | public static bool IsCyclicGraph(this AdjacencyGraph adjacencyGraph) 19 | where TEdge : IEdge => !adjacencyGraph.IsDirectedAcyclicGraph(); 20 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLScalarToExampleValueConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | 4 | namespace GraphQLToKarate.Library.Converters; 5 | 6 | /// 7 | /// Converts a GraphQL type into its associated example value. 8 | /// 9 | public interface IGraphQLScalarToExampleValueConverter 10 | { 11 | /// 12 | /// Converts a GraphQL type into its associated example value. For example, a GraphQL type of "String" would 13 | /// be converted to "\"exampleString\"", a GraphQL type of "Int" would be converted to "1", etc. 14 | /// 15 | /// The GraphQL type to convert. 16 | /// 17 | /// The to use to retrieve additional information about the GraphQL type. 18 | /// 19 | /// The converted GraphQL type. 20 | string Convert(GraphQLType graphQLType, IGraphQLDocumentAdapter graphQLDocumentAdapter); 21 | } 22 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace GraphQLToKarate.Library.Extensions; 4 | 5 | /// 6 | /// A home for extensions on the type. 7 | /// 8 | public static class EnumExtensions 9 | { 10 | /// 11 | /// Retrieve the string representation of the enum. 12 | /// 13 | /// The enum type. 14 | /// The enum value. 15 | /// The string representation of the enum. 16 | /// Thrown when the enum name is unidentifiable. 17 | public static string Name(this TEnum value) 18 | where TEnum : struct, Enum 19 | { 20 | var enumName = Enum.GetName(value); 21 | 22 | if (enumName is null) 23 | { 24 | throw new InvalidEnumArgumentException($"Unable to identify enum name for {value}!"); 25 | } 26 | 27 | return enumName.FirstCharToLower(); 28 | } 29 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Mappings/ICustomScalarMapping.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace GraphQLToKarate.Library.Mappings; 4 | 5 | /// 6 | /// Represents a mapping of GraphQL custom scalar types to Karate data types. 7 | /// 8 | public interface ICustomScalarMapping 9 | { 10 | /// 11 | /// Gets the Karate data type for the specified GraphQL custom scalar type. 12 | /// 13 | /// The GraphQL custom scalar type. 14 | /// The Karate data type to retrieve. 15 | /// true if the mapping was found; otherwise, false. 16 | bool TryGetKarateType( 17 | string graphQLType, 18 | [MaybeNullWhen(false)] out string karateType 19 | ); 20 | 21 | /// 22 | /// Determines whether the mapping contains any custom scalar types. 23 | /// 24 | /// true if the mapping contains any custom scalar types; otherwise, false. 25 | bool Any(); 26 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please provide a meaningful description of what this change will do, or is for. Bonus points for including links to related issues, other PRs, or technical references. 4 | 5 | Note that by _not_ including a description, you are asking reviewers to do extra work to understand the context of this change, which may lead to your PR taking much longer to review, or result in it not being reviewed at all. 6 | 7 | ## Type of Change 8 | 9 | - [ ] Bug Fix 10 | - [ ] New Feature 11 | - [ ] Breaking Change 12 | - [ ] Refactor 13 | - [ ] Documentation 14 | - [ ] Other (please describe) 15 | 16 | ## Checklist 17 | 18 | - [ ] I have read the [contributing guidelines](https://github.com/wbaldoumas/graphql-to-karate/blob/main/CONTRIBUTING.md) 19 | - [ ] Existing issues have been referenced (where applicable) 20 | - [ ] I have verified this change is not present in other open pull requests 21 | - [ ] Functionality is documented 22 | - [ ] All code style checks pass 23 | - [ ] New code contribution is covered by automated tests 24 | - [ ] All new and existing tests pass 25 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/CollectionExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace GraphQLToKarate.Tests.Extensions; 6 | 7 | [TestFixture] 8 | public class CollectionExtensionsTests 9 | { 10 | [Test] 11 | [TestCaseSource(nameof(TestCases))] 12 | public void NoneOrContains_should_return_expected_result( 13 | ICollection collection, 14 | int item, 15 | bool expected) 16 | { 17 | // Act 18 | var actual = collection.NoneOrContains(item); 19 | 20 | // Assert 21 | actual.Should().Be(expected); 22 | } 23 | 24 | private static IEnumerable TestCases 25 | { 26 | get 27 | { 28 | yield return new TestCaseData(Array.Empty(), 1, true).SetName("Empty Collection"); 29 | yield return new TestCaseData(new[] { 1, 2, 3 }, 1, true).SetName("Contains Item"); 30 | yield return new TestCaseData(new[] { 1, 2, 3 }, 4, false).SetName("Does Not ContainItem"); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Will Baldoumas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Mappings/CustomScalarMapping.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Mappings; 2 | 3 | /// 4 | public sealed class CustomScalarMapping(IDictionary mappings) : ICustomScalarMapping 5 | { 6 | public IDictionary Mappings { get; } = mappings; 7 | 8 | public CustomScalarMapping() 9 | : this(new Dictionary(StringComparer.OrdinalIgnoreCase)) 10 | { 11 | } 12 | 13 | public bool TryGetKarateType(string graphQLType, out string karateType) => 14 | Mappings.TryGetValue(graphQLType, out karateType!); 15 | 16 | public bool Any() => Mappings.Any(); 17 | 18 | /// 19 | /// Returns the custom scalar mapping as a string, formatted in a way that is acceptable as a command-line argument. 20 | /// 21 | /// An example response would be "DateTime:string,URL:string,Long:int". 22 | /// 23 | /// The custom scalar mapping as a string. 24 | public override string ToString() => string.Join(',', Mappings.Select(mapping => $"{mapping.Key}:{mapping.Value}")); 25 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/LogCommandSettingsInterceptor.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Settings; 2 | using Serilog.Core; 3 | using Spectre.Console.Cli; 4 | 5 | namespace GraphQLToKarate.CommandLine.Infrastructure; 6 | 7 | /// 8 | /// Intercepts the and sets the accordingly. 9 | /// 10 | internal sealed class LogCommandSettingsInterceptor : ICommandInterceptor 11 | { 12 | public static readonly LoggingLevelSwitch LoggingLevelSwitch = new(); 13 | 14 | /// 15 | /// Intercepts the and sets the accordingly. 16 | /// 17 | /// The command context. 18 | /// The command settings. 19 | public void Intercept(CommandContext context, CommandSettings settings) 20 | { 21 | if (settings is LogCommandSettings logCommandSettings) 22 | { 23 | LoggingLevelSwitch.MinimumLevel = logCommandSettings.LogLevel; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLTypeDefinitionConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Types; 4 | 5 | namespace GraphQLToKarate.Library.Converters; 6 | 7 | /// 8 | /// Converts instances to instances. 9 | /// 10 | public interface IGraphQLTypeDefinitionConverter 11 | { 12 | /// 13 | /// Convert the given to a . 14 | /// 15 | /// The GraphQL object to convert. 16 | /// 17 | /// The GraphQL document adapter, providing access to user-defined types within the GraphQL document. 18 | /// 19 | /// The type of the GraphQL object to convert. 20 | /// The converted . 21 | KarateObject Convert( 22 | T graphQLTypeDefinition, 23 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 24 | where T : GraphQLTypeDefinition, IHasFieldsDefinitionNode; 25 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLNullTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Exceptions; 4 | using GraphQLToKarate.Library.Types; 5 | 6 | namespace GraphQLToKarate.Library.Converters; 7 | 8 | /// 9 | internal sealed class GraphQLNullTypeConverter(IGraphQLTypeConverterFactory graphQLTypeConverterFactory) : IGraphQLTypeConverter 10 | { 11 | public KarateTypeBase Convert( 12 | string graphQLFieldName, 13 | GraphQLType graphQLType, 14 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 15 | { 16 | var graphQLTypeConverter = graphQLType switch 17 | { 18 | GraphQLNamedType => graphQLTypeConverterFactory.CreateGraphQLTypeConverter(), 19 | GraphQLListType => graphQLTypeConverterFactory.CreateGraphQLListTypeConverter(), 20 | _ => throw new InvalidGraphQLTypeException() 21 | }; 22 | 23 | var karateType = graphQLTypeConverter.Convert( 24 | graphQLFieldName, 25 | graphQLType, 26 | graphQLDocumentAdapter 27 | ); 28 | 29 | return new KarateNullType(karateType); 30 | } 31 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Integration.Api/GraphQLToKarate.Integration.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | true 9 | 5fca7578-a673-4a41-bcd3-06db3b52cc6a 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Types; 4 | 5 | namespace GraphQLToKarate.Library.Converters; 6 | 7 | /// 8 | /// Converts a to a . The actual Karate type 9 | /// will differ based on things like nullability, non-nullability, and list-ness. 10 | /// 11 | public interface IGraphQLTypeConverter 12 | { 13 | /// 14 | /// Convert the given to an instance of . 15 | /// 16 | /// The name of the GraphQL field associated with the type. 17 | /// The GraphQL type to convert. 18 | /// 19 | /// The GraphQL document adapter, providing access to user-defined types within the GraphQL document. 20 | /// 21 | /// The converted . 22 | KarateTypeBase Convert( 23 | string graphQLFieldName, 24 | GraphQLType graphQLType, 25 | IGraphQLDocumentAdapter graphQLDocumentAdapter 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/GraphQLDirectiveExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLParser.AST; 3 | using GraphQLToKarate.Library.Apollo; 4 | using GraphQLToKarate.Library.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace GraphQLToKarate.Tests.Extensions; 8 | 9 | [TestFixture] 10 | internal sealed class GraphQLDirectiveExtensionsTests 11 | { 12 | [TestCase(Directives.Inaccessible, true)] 13 | [TestCase(Directives.External, false)] 14 | [TestCase("otherDirective", false)] 15 | public void IsInaccessible_returns_expected_result(string directiveName, bool expectedResult) 16 | { 17 | var directive = new GraphQLDirective(new GraphQLName(directiveName)); 18 | 19 | var result = directive.IsInaccessible(); 20 | 21 | result.Should().Be(expectedResult); 22 | } 23 | 24 | [TestCase(Directives.Inaccessible, false)] 25 | [TestCase(Directives.External, true)] 26 | [TestCase("otherDirective", false)] 27 | public void IsExternal_returns_expected_result(string directiveName, bool expectedResult) 28 | { 29 | var directive = new GraphQLDirective(new GraphQLName(directiveName)); 30 | 31 | var result = directive.IsExternal(); 32 | 33 | result.Should().Be(expectedResult); 34 | } 35 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/TypeRegistrar.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using Spectre.Console.Cli; 4 | 5 | namespace GraphQLToKarate.CommandLine.Infrastructure; 6 | 7 | /// 8 | internal sealed class TypeRegistrar(IHostBuilder hostBuilder) : ITypeRegistrar 9 | { 10 | public ITypeResolver Build() => new TypeResolver(hostBuilder.Build()); 11 | 12 | public void Register(Type serviceType, Type implementationType) => hostBuilder.ConfigureServices( 13 | (_, serviceCollection) => serviceCollection.AddSingleton(serviceType, implementationType) 14 | ); 15 | 16 | public void RegisterInstance(Type serviceType, object implementation) => hostBuilder.ConfigureServices( 17 | (_, serviceCollection) => serviceCollection.AddSingleton(serviceType, implementation) 18 | ); 19 | 20 | public void RegisterLazy(Type serviceType, Func? implementationFactory) 21 | { 22 | if (implementationFactory is null) 23 | { 24 | throw new ArgumentNullException(nameof(implementationFactory)); 25 | } 26 | 27 | hostBuilder.ConfigureServices((_, serviceCollection) => serviceCollection.AddSingleton(serviceType, _ => implementationFactory())); 28 | } 29 | } -------------------------------------------------------------------------------- /.github/workflows/integration-test.yml: -------------------------------------------------------------------------------- 1 | name: Integration Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | integration-test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v6 15 | 16 | - name: 🔨 set up .net 7 17 | uses: actions/setup-dotnet@v5 18 | with: 19 | dotnet-version: 8.0.x 20 | 21 | - name: 🥋 generate karate feature 22 | run: dotnet run --project src/GraphQLToKarate.CommandLine/GraphQLToKarate.CommandLine.csproj convert resources/blog.graphql --non-interactive --configuration-file resources/configuration.json 23 | 24 | - name: 💨 run integration api 25 | run: dotnet run --project tests/GraphQLToKarate.Integration.Api/GraphQLToKarate.Integration.Api.csproj --urls http://localhost:9001/ & 26 | 27 | - name: 🥱 wait for integration api 28 | run: sleep 5s 29 | 30 | - name: 🔨 set up java 17 31 | uses: actions/setup-java@v5 32 | with: 33 | distribution: 'temurin' 34 | java-version: '17' 35 | 36 | - name: 📩 download karate 37 | run: curl -L -o karate.jar https://github.com/karatelabs/karate/releases/download/v1.4.0/karate-1.4.0.jar 38 | 39 | - name: 🥋 run karate 40 | run: java -jar karate.jar blog.feature 41 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLTypeConverterFactory.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace GraphQLToKarate.Library.Converters; 4 | 5 | /// 6 | public sealed class GraphQLTypeConverterFactory(IGraphQLTypeConverter graphQLTypeConverter) : IGraphQLTypeConverterFactory 7 | { 8 | private IGraphQLTypeConverter? _graphQLListTypeConverter; 9 | 10 | private IGraphQLTypeConverter? _graphQLNonNullTypeConverter; 11 | 12 | private IGraphQLTypeConverter? _graphQLNullTypeConverter; 13 | 14 | public IGraphQLTypeConverter CreateGraphQLTypeConverter() => graphQLTypeConverter; 15 | 16 | public IGraphQLTypeConverter CreateGraphQLListTypeConverter() => _graphQLListTypeConverter ??= new GraphQLListTypeConverter(this); 17 | 18 | public IGraphQLTypeConverter CreateGraphQLNonNullTypeConverter() => _graphQLNonNullTypeConverter ??= new GraphQLNonNullTypeConverter(this); 19 | 20 | public IGraphQLTypeConverter CreateGraphQLNullTypeConverter() => _graphQLNullTypeConverter ??= new GraphQLNullTypeConverter(this); 21 | 22 | public IGraphQLTypeConverter CreateGraphQLTypeConverter(GraphQLType graphQLType) => graphQLType switch 23 | { 24 | GraphQLNonNullType => CreateGraphQLNonNullTypeConverter(), 25 | _ => CreateGraphQLNullTypeConverter() 26 | }; 27 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLFieldDefinitionConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Enums; 4 | using GraphQLToKarate.Library.Types; 5 | 6 | namespace GraphQLToKarate.Library.Converters; 7 | 8 | /// 9 | /// Converts instances for GraphQL queries to instances. 10 | /// 11 | public interface IGraphQLFieldDefinitionConverter 12 | { 13 | /// 14 | /// Convert the given of a GraphQL query to a . 15 | /// 16 | /// The query field to convert. 17 | /// 18 | /// The GraphQL document adapter, providing access to user-defined types within the GraphQL document. 19 | /// 20 | /// The GraphQL operation type (query vs mutation). 21 | /// The converted . 22 | GraphQLOperation Convert( 23 | GraphQLFieldDefinition graphQLFieldDefinition, 24 | IGraphQLDocumentAdapter graphQLDocumentAdapter, 25 | GraphQLOperationType graphQLOperationType 26 | ); 27 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/GraphQLToKarate.Library.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLNonNullTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Exceptions; 4 | using GraphQLToKarate.Library.Types; 5 | 6 | namespace GraphQLToKarate.Library.Converters; 7 | 8 | /// 9 | internal sealed class GraphQLNonNullTypeConverter(IGraphQLTypeConverterFactory graphQLTypeConverterFactory) : IGraphQLTypeConverter 10 | { 11 | public KarateTypeBase Convert( 12 | string graphQLFieldName, 13 | GraphQLType graphQLType, 14 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 15 | { 16 | var graphQLNonNullType = graphQLType as GraphQLNonNullType; 17 | var graphQLInnerType = graphQLNonNullType!.Type; 18 | 19 | var graphQLTypeConverter = graphQLInnerType switch 20 | { 21 | GraphQLNamedType => graphQLTypeConverterFactory.CreateGraphQLTypeConverter(), 22 | GraphQLListType => graphQLTypeConverterFactory.CreateGraphQLListTypeConverter(), 23 | _ => throw new InvalidGraphQLTypeException() 24 | }; 25 | 26 | var karateType = graphQLTypeConverter.Convert( 27 | graphQLFieldName, 28 | graphQLInnerType, 29 | graphQLDocumentAdapter 30 | ); 31 | 32 | return new KarateNonNullType(karateType); 33 | } 34 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/IGraphQLInputValueDefinitionConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Types; 4 | 5 | namespace GraphQLToKarate.Library.Converters; 6 | 7 | /// 8 | /// Converts instances to . 9 | /// 10 | public interface IGraphQLInputValueDefinitionConverter 11 | { 12 | /// 13 | /// Convert the given to a . 14 | /// 15 | /// The input value definition to convert. 16 | /// The adapter to use for accessing the GraphQL document. 17 | /// The converted GraphQL argument type. 18 | GraphQLArgumentTypeBase Convert( 19 | GraphQLInputValueDefinition graphQLInputValueDefinition, 20 | IGraphQLDocumentAdapter graphQLDocumentAdapter 21 | ); 22 | 23 | /// 24 | /// Get all the previously converted types. 25 | /// 26 | /// All the previously converted types. 27 | ICollection GetAllConverted(); 28 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policies and Procedures 2 | 3 | This document outlines security procedures and general policies for the 4 | `graphql-to-karate` project. 5 | 6 | - [Security Policies and Procedures](#security-policies-and-procedures) 7 | - [Reporting a Bug](#reporting-a-bug) 8 | - [Disclosure Policy](#disclosure-policy) 9 | - [Comments on this Policy](#comments-on-this-policy) 10 | 11 | ## Reporting a Bug 12 | 13 | The `graphql-to-karate` team and community take all security bugs in 14 | `graphql-to-karate` seriously. Thank you for improving the security of 15 | `graphql-to-karate`. We appreciate your efforts and responsible disclosure and 16 | will make every effort to acknowledge your contributions. 17 | 18 | [Report a vulnerability](https://github.com/wbaldoumas/graphql-to-karate/security/advisories/new). 19 | 20 | ## Disclosure Policy 21 | 22 | When the security team receives a security bug report, they will assign it to a 23 | primary handler. This person will coordinate the fix and release process, 24 | involving the following steps: 25 | 26 | - Confirm the problem and determine the affected versions. 27 | - Audit code to find any potential similar problems. 28 | - Prepare fixes for all releases still under maintenance. These fixes will be 29 | released as quickly as possible. 30 | 31 | ## Comments on this Policy 32 | 33 | If you have suggestions on how this process could be improved please submit a 34 | pull request. 35 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLTypeDefinitionConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Extensions; 4 | using GraphQLToKarate.Library.Types; 5 | 6 | namespace GraphQLToKarate.Library.Converters; 7 | 8 | /// 9 | public sealed class GraphQLTypeDefinitionConverter(IGraphQLTypeConverterFactory graphQLTypeConverterFactory) : IGraphQLTypeDefinitionConverter 10 | { 11 | public KarateObject Convert( 12 | T graphQLTypeDefinition, 13 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 14 | where T : GraphQLTypeDefinition, IHasFieldsDefinitionNode 15 | { 16 | if (graphQLTypeDefinition.Fields is null) 17 | { 18 | return new KarateObject(graphQLTypeDefinition.NameValue(), new List()); 19 | } 20 | 21 | var karateTypes = 22 | from graphQLFieldDefinition in graphQLTypeDefinition.Fields 23 | let converter = graphQLTypeConverterFactory.CreateGraphQLTypeConverter(graphQLFieldDefinition.Type) 24 | select converter.Convert( 25 | graphQLFieldDefinition.NameValue(), 26 | graphQLFieldDefinition.Type, 27 | graphQLDocumentAdapter 28 | ); 29 | 30 | return new KarateObject(graphQLTypeDefinition.NameValue(), karateTypes.ToList()); 31 | } 32 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/GraphQLDirectiveExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Apollo; 3 | 4 | namespace GraphQLToKarate.Library.Extensions; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | internal static class GraphQLDirectiveExtensions 10 | { 11 | /// 12 | /// Returns true if the directive is the Apollo Federation directive. 13 | /// 14 | /// The directive to check. 15 | /// True if the directive is the Apollo Federation directive. 16 | public static bool IsInaccessible(this GraphQLDirective graphQLDirective) => graphQLDirective.NameValue() 17 | .Equals(Directives.Inaccessible, StringComparison.OrdinalIgnoreCase); 18 | 19 | /// 20 | /// Returns true if the directive is the Apollo Federation directive. 21 | /// 22 | /// The directive to check. 23 | /// True if the directive is the Apollo Federation directive. 24 | public static bool IsExternal(this GraphQLDirective graphQLDirective) => 25 | graphQLDirective.NameValue().Equals(Directives.External, StringComparison.OrdinalIgnoreCase); 26 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/GraphQLFieldDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Apollo; 3 | 4 | namespace GraphQLToKarate.Library.Extensions; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | internal static class GraphQLFieldDefinitionExtensions 10 | { 11 | /// 12 | /// Returns true if the field is the Apollo Federation directive. 13 | /// 14 | /// The field to check. 15 | /// True if the field is the Apollo Federation directive. 16 | public static bool IsInaccessible(this GraphQLFieldDefinition graphQLFieldDefinition) => 17 | graphQLFieldDefinition.Directives?.Any(directive => directive.IsInaccessible()) ?? false; 18 | 19 | /// 20 | /// Returns true if the field is the Apollo Federation directive. 21 | /// 22 | /// The field to check. 23 | /// True if the field is the Apollo Federation directive. 24 | public static bool IsExternal(this GraphQLFieldDefinition graphQLFieldDefinition) => 25 | graphQLFieldDefinition.Directives?.Any(directive => directive.IsExternal()) ?? false; 26 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLListTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Exceptions; 4 | using GraphQLToKarate.Library.Types; 5 | 6 | namespace GraphQLToKarate.Library.Converters; 7 | 8 | /// 9 | internal sealed class GraphQLListTypeConverter(IGraphQLTypeConverterFactory graphQLTypeConverterFactory) : IGraphQLTypeConverter 10 | { 11 | public KarateTypeBase Convert( 12 | string graphQLFieldName, 13 | GraphQLType graphQLType, 14 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 15 | { 16 | var graphQLListType = graphQLType as GraphQLListType; 17 | var graphQLInnerType = graphQLListType!.Type; 18 | 19 | var graphQLTypeConverter = graphQLInnerType switch 20 | { 21 | GraphQLNonNullType => graphQLTypeConverterFactory.CreateGraphQLNonNullTypeConverter(), 22 | GraphQLNamedType => graphQLTypeConverterFactory.CreateGraphQLNullTypeConverter(), 23 | GraphQLListType => graphQLTypeConverterFactory.CreateGraphQLNullTypeConverter(), 24 | _ => throw new InvalidGraphQLTypeException() 25 | }; 26 | 27 | var karateType = graphQLTypeConverter.Convert( 28 | graphQLFieldName, 29 | graphQLInnerType, 30 | graphQLDocumentAdapter 31 | ); 32 | 33 | return new KarateListType(karateType); 34 | } 35 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Mappers/GraphQLToKarateUserConfigurationMapper.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Settings; 2 | using GraphQLToKarate.Library.Mappings; 3 | 4 | namespace GraphQLToKarate.CommandLine.Mappers; 5 | 6 | /// > 7 | internal sealed class GraphQLToKarateUserConfigurationMapper : IGraphQLToKarateUserConfigurationMapper 8 | { 9 | public LoadedConvertCommandSettings Map(GraphQLToKarateUserConfiguration graphQLToKarateUserConfiguration) => new() 10 | { 11 | GraphQLSchema = graphQLToKarateUserConfiguration.GraphQLSchema, 12 | InputFile = graphQLToKarateUserConfiguration.InputFile, 13 | OutputFile = graphQLToKarateUserConfiguration.OutputFile, 14 | BaseUrl = graphQLToKarateUserConfiguration.BaseUrl, 15 | QueryName = graphQLToKarateUserConfiguration.QueryName, 16 | MutationName = graphQLToKarateUserConfiguration.MutationName, 17 | ExcludeQueries = graphQLToKarateUserConfiguration.ExcludeQueries, 18 | CustomScalarMapping = new CustomScalarMapping(graphQLToKarateUserConfiguration.CustomScalarMapping), 19 | IncludeMutations = graphQLToKarateUserConfiguration.IncludeMutations, 20 | QueryOperationFilter = graphQLToKarateUserConfiguration.QueryOperationFilter, 21 | MutationOperationFilter = graphQLToKarateUserConfiguration.MutationOperationFilter, 22 | TypeFilter = graphQLToKarateUserConfiguration.TypeFilter 23 | }; 24 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/LogLevelVerbosityConverter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | 5 | namespace GraphQLToKarate.CommandLine.Infrastructure; 6 | 7 | /// 8 | /// Converts a log-level string into a . 9 | /// 10 | internal sealed class LogLevelVerbosityConverter : TypeConverter 11 | { 12 | private readonly Dictionary _logLevelLookup = new(StringComparer.OrdinalIgnoreCase) 13 | { 14 | { "verbose", LogEventLevel.Verbose }, 15 | { "debug", LogEventLevel.Debug }, 16 | { "information", LogEventLevel.Information }, 17 | { "warning", LogEventLevel.Warning }, 18 | { "error", LogEventLevel.Error }, 19 | { "fatal", LogEventLevel.Fatal } 20 | }; 21 | 22 | public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) 23 | { 24 | if (value is not string stringValue) 25 | { 26 | throw new NotSupportedException("Can't convert log level value to log level verbosity."); 27 | } 28 | 29 | if (_logLevelLookup.TryGetValue(stringValue, out var verbosity)) 30 | { 31 | return verbosity; 32 | } 33 | 34 | const string format = "The value '{0}' is not a valid log level verbosity."; 35 | 36 | throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, format, value)); 37 | } 38 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/GraphQLEnumValueDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Apollo; 3 | 4 | namespace GraphQLToKarate.Library.Extensions; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | internal static class GraphQLEnumValueDefinitionExtensions 10 | { 11 | /// 12 | /// Returns true if the enum value definition has the directive. 13 | /// 14 | /// The enum value definition to check. 15 | /// True if the enum value definition has the directive. 16 | public static bool IsInaccessible(this GraphQLEnumValueDefinition graphQLEnumValueDefinition) => 17 | graphQLEnumValueDefinition.Directives?.Any(directive => directive.IsInaccessible()) ?? false; 18 | 19 | /// 20 | /// Returns true if the enum value definition has the directive. 21 | /// 22 | /// The enum value definition to check. 23 | /// True if the enum value definition has the directive. 24 | public static bool IsExternal(this GraphQLEnumValueDefinition graphQLEnumValueDefinition) => 25 | graphQLEnumValueDefinition.Directives?.Any(directive => directive.IsExternal()) ?? false; 26 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/GraphQLInputValueDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Apollo; 3 | 4 | namespace GraphQLToKarate.Library.Extensions; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | internal static class GraphQLInputValueDefinitionExtensions 10 | { 11 | /// 12 | /// Returns true if the input value definition has the directive. 13 | /// 14 | /// The input value definition to check. 15 | /// True if the input value definition has the directive. 16 | public static bool IsInaccessible(this GraphQLInputValueDefinition graphQLInputValueDefinition) => 17 | graphQLInputValueDefinition.Directives?.Any(directive => directive.IsInaccessible()) ?? false; 18 | 19 | /// 20 | /// Returns true if the input value definition has the directive. 21 | /// 22 | /// The input value definition to check. 23 | /// True if the input value definition has the directive. 24 | public static bool IsExternal(this GraphQLInputValueDefinition graphQLInputValueDefinition) => 25 | graphQLInputValueDefinition.Directives?.Any(directive => directive.IsExternal()) ?? false; 26 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | 15 | - name: 🔨 set up .net 7 16 | uses: actions/setup-dotnet@v5 17 | with: 18 | dotnet-version: 8.0.x 19 | 20 | - name: ⚗ restore dependencies 21 | run: dotnet restore src 22 | 23 | - name: 🛠 build 24 | run: dotnet build src --configuration Release --no-restore 25 | 26 | - name: 🧪 run tests 27 | run: | 28 | cd ./tests/GraphQLToKarate.Tests/ 29 | dotnet test --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover 30 | cp TestResults/*/coverage.opencover.xml . 31 | 32 | - name: 🧪 run command line tests 33 | run: | 34 | cd ./tests/GraphQLToKarate.CommandLine.Tests/ 35 | dotnet test --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover 36 | cp TestResults/*/coverage.opencover.xml . 37 | 38 | - name: 💌 publish code coverage 39 | uses: codecov/codecov-action@v5 40 | with: 41 | token: ${{ secrets.CODECOV_TOKEN }} 42 | files: /home/runner/work/graphql-to-karate/graphql-to-karate/tests/GraphQLToKarate.Tests/coverage.opencover.xml,/home/runner/work/graphql-to-karate/graphql-to-karate/tests/GraphQLToKarate.CommandLine.Tests/coverage.opencover.xml 43 | fail_ci_if_error: true 44 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Extensions; 4 | using GraphQLToKarate.Library.Tokens; 5 | using GraphQLToKarate.Library.Types; 6 | 7 | namespace GraphQLToKarate.Library.Converters; 8 | 9 | /// 10 | internal sealed class GraphQLTypeConverter : IGraphQLTypeConverter 11 | { 12 | public KarateTypeBase Convert( 13 | string graphQLFieldName, 14 | GraphQLType graphQLType, 15 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 16 | { 17 | var karateTypeSchema = graphQLType.GetUnwrappedTypeName() switch 18 | { 19 | GraphQLToken.Id => KarateToken.String, 20 | GraphQLToken.String => KarateToken.String, 21 | GraphQLToken.Int => KarateToken.Number, 22 | GraphQLToken.Float => KarateToken.Number, 23 | GraphQLToken.Boolean => KarateToken.Boolean, 24 | { } graphQLTypeName when graphQLDocumentAdapter.IsGraphQLUnionTypeDefinition(graphQLTypeName) => 25 | KarateToken.Present, 26 | { } graphQLTypeName when graphQLDocumentAdapter.IsGraphQLEnumTypeDefinition(graphQLTypeName) => 27 | KarateToken.String, 28 | { } graphQLTypeName when graphQLDocumentAdapter.IsGraphQLTypeDefinitionWithFields(graphQLTypeName) => 29 | $"({graphQLTypeName.FirstCharToLower()}Schema)", 30 | _ => KarateToken.Present 31 | }; 32 | 33 | return new KarateType(karateTypeSchema, graphQLFieldName); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Converters/GraphQLTypeConverterFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLParser.AST; 3 | using GraphQLToKarate.Library.Converters; 4 | using NUnit.Framework; 5 | 6 | namespace GraphQLToKarate.Tests.Converters; 7 | 8 | [TestFixture] 9 | internal sealed class GraphQLTypeConverterFactoryTests 10 | { 11 | private GraphQLTypeConverterFactory? _subjectUnderTest; 12 | 13 | [SetUp] 14 | public void SetUp() => _subjectUnderTest = new GraphQLTypeConverterFactory(new GraphQLTypeConverter()); 15 | 16 | [Test] 17 | [TestCaseSource(nameof(TypeConverterTestCases))] 18 | public void CreateGraphQLTypeConverter_Returns_Expected_Type_Instance( 19 | GraphQLType graphQLType, 20 | Type expectedType) 21 | { 22 | // act 23 | var typeConverter = _subjectUnderTest!.CreateGraphQLTypeConverter(graphQLType); 24 | 25 | // assert 26 | typeConverter.Should().BeOfType(expectedType); 27 | } 28 | 29 | private static IEnumerable TypeConverterTestCases 30 | { 31 | get 32 | { 33 | yield return new TestCaseData( 34 | new GraphQLNonNullType(new GraphQLNamedType(new GraphQLName("foo"))), 35 | typeof(GraphQLNonNullTypeConverter) 36 | ).SetName("Returns GraphQLNonNullTypeConverter for GraphQLNonNullType input"); 37 | 38 | yield return new TestCaseData( 39 | new GraphQLNamedType(new GraphQLName("bar")), 40 | typeof(GraphQLNullTypeConverter) 41 | ).SetName("Returns GraphQLNullTypeConverter for other GraphQLType input"); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Types/KarateObject.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Extensions; 2 | using GraphQLToKarate.Library.Tokens; 3 | using System.Text; 4 | 5 | namespace GraphQLToKarate.Library.Types; 6 | 7 | /// 8 | /// A Karate schema object. The method has been overridden to allow for 9 | /// easy printing of the object's Karate schema. 10 | /// 11 | public sealed class KarateObject 12 | { 13 | public string Name { get; } 14 | 15 | private readonly Lazy _schemaString; 16 | 17 | private readonly IReadOnlyCollection _karateTypes; 18 | 19 | public KarateObject(string name, IReadOnlyCollection karateTypes) 20 | { 21 | Name = name; 22 | _karateTypes = karateTypes; 23 | _schemaString = new Lazy(GenerateSchemaString); 24 | } 25 | 26 | public override string ToString() => _schemaString.Value; 27 | 28 | private string GenerateSchemaString() 29 | { 30 | var stringBuilder = new StringBuilder(); 31 | 32 | stringBuilder.Append(SchemaToken.OpenBrace); 33 | 34 | foreach (var karateType in _karateTypes) 35 | { 36 | stringBuilder.AppendLine(); 37 | stringBuilder.Append(SchemaToken.Indent); 38 | stringBuilder.Append($"{karateType.Name}: '{karateType.Schema}'"); 39 | stringBuilder.Append(SchemaToken.Comma); 40 | } 41 | 42 | stringBuilder.TrimEnd(1); // remove trailing comma 43 | stringBuilder.AppendLine(); 44 | stringBuilder.Append(SchemaToken.CloseBrace); 45 | 46 | return stringBuilder.ToString(); 47 | } 48 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Settings/GraphQLToKarateUserConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace GraphQLToKarate.CommandLine.Settings; 5 | 6 | /// 7 | /// A class that represents the user settings, read from a configuration file. 8 | /// 9 | [ExcludeFromCodeCoverage] 10 | public sealed class GraphQLToKarateUserConfiguration 11 | { 12 | [JsonIgnore] 13 | public string GraphQLSchema { get; set; } = string.Empty; 14 | 15 | [JsonIgnore] 16 | public string InputFile { get; set; } = string.Empty; 17 | 18 | public string OutputFile { get; set; } = Options.OutputFileOptionDefaultValue; 19 | 20 | public string QueryName { get; set; } = Options.QueryNameOptionDefaultValue; 21 | 22 | public string MutationName { get; set; } = Options.MutationNameOptionDefaultValue; 23 | 24 | public bool ExcludeQueries { get; set; } = Options.ExcludeQueriesOptionDefaultValue; 25 | 26 | public bool IncludeMutations { get; set; } = Options.IncludeMutationsOptionDefaultValue; 27 | 28 | public string BaseUrl { get; set; } = Options.BaseUrlOptionDefaultValue; 29 | 30 | public IDictionary CustomScalarMapping { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); 31 | 32 | public ISet QueryOperationFilter { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); 33 | 34 | public ISet MutationOperationFilter { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); 35 | 36 | public ISet TypeFilter { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); 37 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Infrastructure/TypeRegistrarTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Infrastructure; 3 | using Microsoft.Extensions.Hosting; 4 | using NUnit.Framework; 5 | using Spectre.Console.Testing; 6 | 7 | namespace GraphQLToKarate.CommandLine.Tests.Infrastructure; 8 | 9 | [TestFixture] 10 | internal sealed class TypeRegistrarTests 11 | { 12 | private TypeRegistrarBaseTests? _tests; 13 | 14 | [SetUp] 15 | public void SetUp() => _tests = new TypeRegistrarBaseTests(() => TypeRegistrarConfigurator.ConfigureTypeRegistrar(Array.Empty())); 16 | 17 | [Test] 18 | public void Test() => _tests!.RunAllTests(); 19 | 20 | [Test] 21 | public void RegisterLazy_registers_type_as_expected() 22 | { 23 | // arrange 24 | var subjectUnderTest = new TypeRegistrar(HostConfigurator.ConfigureHostBuilder(Array.Empty())); 25 | 26 | // act 27 | subjectUnderTest.RegisterLazy(typeof(IHostBuilder), () => HostConfigurator.ConfigureHostBuilder(Array.Empty())); 28 | 29 | // assert 30 | subjectUnderTest.Build().Resolve(typeof(IHostBuilder)).Should().BeEquivalentTo(HostConfigurator.ConfigureHostBuilder(Array.Empty())); 31 | } 32 | 33 | [Test] 34 | public void RegisterLazy_throws_exception_when_registration_func_is_null() 35 | { 36 | // arrange 37 | var hostBuilder = HostConfigurator.ConfigureHostBuilder(Array.Empty()); 38 | var subjectUnderTest = new TypeRegistrar(hostBuilder); 39 | 40 | // act 41 | var act = () => subjectUnderTest.RegisterLazy(typeof(IHostBuilder), null); 42 | 43 | // assert 44 | act.Should().Throw(); 45 | } 46 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/StringExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace GraphQLToKarate.Tests.Extensions; 6 | 7 | [TestFixture] 8 | internal sealed class StringExtensionsTests 9 | { 10 | [Test] 11 | [TestCase("all lowercase", "all lowercase")] 12 | [TestCase("ALL UPPERCASE", "aLL UPPERCASE")] 13 | [TestCase("Mixed Case", "mixed Case")] 14 | [TestCase("1 has numbers", "1 has numbers")] 15 | [TestCase("% has symbols", "% has symbols")] 16 | [TestCase(null, "")] 17 | [TestCase("", "")] 18 | public void FirstCharToLowerTest(string? input, string expectedOutput) => 19 | input?.FirstCharToLower().Should().Be(expectedOutput); 20 | 21 | [Test] 22 | [TestCase("all lowercase", "All lowercase")] 23 | [TestCase("ALL UPPERCASE", "ALL UPPERCASE")] 24 | [TestCase("mixed Case", "Mixed Case")] 25 | [TestCase("1 has numbers", "1 has numbers")] 26 | [TestCase("% has symbols", "% has symbols")] 27 | [TestCase(null, "")] 28 | [TestCase("", "")] 29 | public void FirstCharToUpperTest(string? input, string expectedOutput) => 30 | input?.FirstCharToUpper().Should().Be(expectedOutput); 31 | 32 | [Test] 33 | [TestCase("foo", 1, " foo")] 34 | [TestCase("foo", 2, " foo")] 35 | [TestCase("foo", 3, " foo")] 36 | [TestCase( 37 | """ 38 | this is 39 | a multi-line 40 | string 41 | """, 42 | 4, 43 | """ 44 | this is 45 | a multi-line 46 | string 47 | """ 48 | )] 49 | public void IndentTest(string input, int indent, string expectedOutput) => 50 | input.Indent(indent).Should().Be(expectedOutput); 51 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.2.0] 2024-02-14 9 | 10 | ### Changes 11 | 12 | No feature changes, but potential for performance improvements due to internal upgrades. 13 | 14 | - Upgrade to .NET 8.0 15 | - Leverage newer C# language features for cleaner code 16 | - Various dependency updates 17 | 18 | ## [0.1.3] 2023-07-29 19 | 20 | ### Changed 21 | 22 | Dependency updates: 23 | | Library | Current Version | New Version | 24 | |---------|-----------------|-------------| 25 | | GraphQL-Parser | 9.1.0 | 9.2.0 | 26 | | Microsoft.Extensions.Logging.Abstractions | 7.0.0 | 7.0.1 | 27 | | Serilog.Extensions.Hosting | 5.0.1 | 7.0.0 | 28 | | Spectre.Console | 0.46.0 | 0.47.0 | 29 | | Spectre.Console.Analyzer | 0.46.0 | 0.47.0 | 30 | | Spectre.Console.Cli | 0.46.0 | 0.47.0 | 31 | | System.IO.Abstractions | 19.2.22 | 19.2.29 | 32 | 33 | ## [0.1.2] 2023-05-07 34 | 35 | ### Changed 36 | 37 | - Update NuGet Packages - Minor Updates ([#108](https://github.com/wbaldoumas/graphql-to-karate/pull/108)) 38 | 39 | ## [0.1.1] 2023-04-29 40 | 41 | ### Added 42 | 43 | - Example JSON Configuration ([#105](https://github.com/wbaldoumas/graphql-to-karate/pull/105)) 44 | 45 | ### Changed 46 | 47 | - Update NuGet Packages - Minor Updates ([#102](https://github.com/wbaldoumas/graphql-to-karate/pull/102)) 48 | - Copy Changelog to Release ([#103](https://github.com/wbaldoumas/graphql-to-karate/pull/103)) 49 | - Tweak README.md Content ([#104](https://github.com/wbaldoumas/graphql-to-karate/pull/104)) 50 | - Cleanup ([#106](https://github.com/wbaldoumas/graphql-to-karate/pull/106)) 51 | 52 | ## [0.1.0] 2023-04-22 53 | 54 | ### Released 55 | 56 | Initial beta release. 🎉 57 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Infrastructure/TypeResolverTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Infrastructure; 3 | using Microsoft.Extensions.Hosting; 4 | using NSubstitute; 5 | using NUnit.Framework; 6 | 7 | namespace GraphQLToKarate.CommandLine.Tests.Infrastructure; 8 | 9 | [TestFixture] 10 | internal sealed class TypeResolverTests 11 | { 12 | [Test] 13 | [TestCase(typeof(int), null)] 14 | [TestCase(null, null)] 15 | public void TypeResolver_resolves_expected_type(Type? requestedType, object? expectedResolvedObject) 16 | { 17 | // arrange 18 | var mockHost = Substitute.For(); 19 | var mockServiceProvider = Substitute.For(); 20 | 21 | mockHost.Services.Returns(mockServiceProvider); 22 | mockServiceProvider.GetService(Arg.Any()).Returns(expectedResolvedObject); 23 | 24 | var subjectUnderTest = new TypeResolver(mockHost); 25 | 26 | // act 27 | var resolvedObject = subjectUnderTest.Resolve(requestedType); 28 | 29 | // assert 30 | resolvedObject.Should().BeEquivalentTo(expectedResolvedObject, "because the service provider returned it."); 31 | } 32 | 33 | [Test] 34 | public void TypeResolver_throws_exception_when_null_ServiceProvider_passed() 35 | { 36 | // arrange + act 37 | var act = () => new TypeResolver(null); 38 | 39 | // assert 40 | act.Should().Throw(); 41 | } 42 | 43 | [Test] 44 | public void TypeResolver_properly_disposes_service_provider_when_it_is_disposable() 45 | { 46 | // arrange 47 | var host = Substitute.For(); 48 | var subjectUnderTest = new TypeResolver(host); 49 | 50 | // act 51 | subjectUnderTest.Dispose(); 52 | 53 | // assert 54 | (host as IDisposable)!.Received().Dispose(); 55 | } 56 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Types/GraphQLVariableTypeTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Tokens; 3 | using GraphQLToKarate.Library.Types; 4 | using NUnit.Framework; 5 | 6 | namespace GraphQLToKarate.Tests.Types; 7 | 8 | [TestFixture] 9 | internal sealed class GraphQLVariableTypeTests 10 | { 11 | [Test] 12 | [TestCaseSource(nameof(TestCases))] 13 | public void GraphQLVariableType_Schema_produces_expected_output( 14 | GraphQLArgumentTypeBase graphQLArgumentTypeBase, 15 | string expectedSchema 16 | ) => graphQLArgumentTypeBase.VariableTypeName.Should().Be(expectedSchema); 17 | 18 | private static IEnumerable TestCases 19 | { 20 | get 21 | { 22 | var graphQLVariableType = new GraphQLArgumentType("id", "id", GraphQLToken.String, "\"an example value\""); 23 | 24 | yield return new TestCaseData( 25 | graphQLVariableType, 26 | GraphQLToken.String 27 | ); 28 | 29 | yield return new TestCaseData( 30 | new GraphQLNonNullArgumentType(graphQLVariableType), 31 | $"{GraphQLToken.String}{GraphQLToken.NonNull}" 32 | ); 33 | 34 | yield return new TestCaseData( 35 | new GraphQLListArgumentType(graphQLVariableType), 36 | $"{SchemaToken.OpenBracket}{GraphQLToken.String}{SchemaToken.CloseBracket}" 37 | ); 38 | 39 | yield return new TestCaseData( 40 | new GraphQLNonNullArgumentType( 41 | new GraphQLListArgumentType( 42 | new GraphQLNonNullArgumentType( 43 | graphQLVariableType 44 | ) 45 | ) 46 | ), 47 | $"{SchemaToken.OpenBracket}{GraphQLToken.String}{GraphQLToken.NonNull}{SchemaToken.CloseBracket}{GraphQLToken.NonNull}" 48 | ); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Infrastructure/LogCommandSettingsInterceptorTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Infrastructure; 3 | using GraphQLToKarate.CommandLine.Settings; 4 | using NSubstitute; 5 | using NUnit.Framework; 6 | using Serilog.Events; 7 | using Spectre.Console.Cli; 8 | 9 | namespace GraphQLToKarate.CommandLine.Tests.Infrastructure; 10 | 11 | [TestFixture] 12 | internal sealed class LogCommandSettingsInterceptorTests 13 | { 14 | private LogCommandSettingsInterceptor? _subjectUnderTest; 15 | 16 | [SetUp] 17 | public void SetUp() => _subjectUnderTest = new LogCommandSettingsInterceptor(); 18 | 19 | [Test] 20 | public void LogLevelInterceptor_sets_log_level_as_expected() 21 | { 22 | // arrange 23 | const LogEventLevel expectedLogLevel = LogEventLevel.Verbose; 24 | 25 | var command = new LogCommandSettings 26 | { 27 | LogLevel = expectedLogLevel 28 | }; 29 | 30 | // act 31 | _subjectUnderTest!.Intercept( 32 | new CommandContext(Substitute.For(), string.Empty, null), 33 | command 34 | ); 35 | 36 | // assert 37 | LogCommandSettingsInterceptor.LoggingLevelSwitch.MinimumLevel 38 | .Should() 39 | .Be(expectedLogLevel); 40 | } 41 | 42 | [Test] 43 | public void LogLevelInterceptor_does_not_set_log_level_when_settings_is_not_LogCommandSettings() 44 | { 45 | // arrange 46 | LogCommandSettingsInterceptor.LoggingLevelSwitch.MinimumLevel = LogEventLevel.Fatal; 47 | 48 | var command = Substitute.For(); 49 | 50 | // act 51 | _subjectUnderTest!.Intercept( 52 | new CommandContext(Substitute.For(), string.Empty, null), 53 | command 54 | ); 55 | 56 | // assert 57 | LogCommandSettingsInterceptor.LoggingLevelSwitch.MinimumLevel 58 | .Should() 59 | .Be(LogEventLevel.Fatal); 60 | } 61 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Infrastructure/LogLevelVerbosityConverterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Infrastructure; 3 | using NUnit.Framework; 4 | using Serilog.Events; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | 8 | namespace GraphQLToKarate.CommandLine.Tests.Infrastructure; 9 | 10 | [TestFixture] 11 | internal sealed class LogLevelVerbosityConverterTests 12 | { 13 | private TypeConverter? _subjectUnderTest; 14 | 15 | [SetUp] 16 | public void SetUp() => _subjectUnderTest = new LogLevelVerbosityConverter(); 17 | 18 | [Test] 19 | [TestCase("verbose", LogEventLevel.Verbose)] 20 | [TestCase("debug", LogEventLevel.Debug)] 21 | [TestCase("information", LogEventLevel.Information)] 22 | [TestCase("warning", LogEventLevel.Warning)] 23 | [TestCase("error", LogEventLevel.Error)] 24 | [TestCase("fatal", LogEventLevel.Fatal)] 25 | public void ConvertFrom_should_return_expected_LogEventLevel_when_given_valid_input( 26 | string input, 27 | LogEventLevel expectedLogEventLevel 28 | ) => _subjectUnderTest! 29 | .ConvertFrom(null, CultureInfo.InvariantCulture, input) 30 | .Should() 31 | .Be(expectedLogEventLevel); 32 | 33 | [Test] 34 | public void ConvertFrom_should_throw_InvalidOperationException_when_given_invalid_input() 35 | { 36 | // arrange + act 37 | var act = () => _subjectUnderTest!.ConvertFrom(null, CultureInfo.InvariantCulture, "invalid"); 38 | 39 | // assert 40 | act.Should() 41 | .Throw() 42 | .WithMessage("The value 'invalid' is not a valid log level verbosity."); 43 | } 44 | 45 | [Test] 46 | public void ConvertFrom_should_throw_NotSupportedException_when_given_non_string_input() 47 | { 48 | // arrange + act 49 | var act = () => _subjectUnderTest!.ConvertFrom(null, CultureInfo.InvariantCulture, 123); 50 | 51 | // assert 52 | act.Should() 53 | .Throw() 54 | .WithMessage("Can't convert log level value to log level verbosity."); 55 | } 56 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/GraphQLToKarate.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | all 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | 36 | 37 | 38 | all 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /resources/blog.graphql: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | scalar URL 3 | 4 | type Query { 5 | userById(id: ID!): User! 6 | blogPostById(id: ID!): BlogPost! 7 | commentById(id: ID!): Comment! 8 | users(pageInfo: PageInfoInput): UserConnection! 9 | blogPosts(pageInfo: PageInfoInput): BlogPostConnection! 10 | comments(pageInfo: PageInfoInput): CommentConnection! 11 | search(query: String!): [SearchResult!]! 12 | } 13 | 14 | type Mutation { 15 | createUser(input: CreateUserInput!): User! 16 | updateUser(input: UpdateUserInput!): User! 17 | deleteUser(id: ID!): Boolean! 18 | } 19 | 20 | enum UserRole { 21 | GUEST 22 | STANDARD 23 | ADMINISTRATOR 24 | } 25 | 26 | interface Node { 27 | id: ID! 28 | } 29 | 30 | type User implements Node { 31 | name: String! 32 | role: UserRole! 33 | blogPosts: [BlogPost!]! 34 | createdAt: DateTime! 35 | updatedAt: DateTime! 36 | id: ID! 37 | } 38 | 39 | type BlogPost implements Node { 40 | title: String! 41 | content: String! 42 | author: User! 43 | comments: [Comment!]! 44 | createdAt: DateTime! 45 | updatedAt: DateTime! 46 | id: ID! 47 | } 48 | 49 | extend type BlogPost { 50 | uri: URL! 51 | } 52 | 53 | type Comment implements Node { 54 | content: String! 55 | author: User! 56 | blogPost: BlogPost! 57 | createdAt: DateTime! 58 | updatedAt: DateTime! 59 | id: ID! 60 | } 61 | 62 | type PageInfo { 63 | hasNextPage: Boolean! 64 | hasPreviousPage: Boolean! 65 | totalCount: Int! 66 | } 67 | 68 | type UserConnection { 69 | nodes: [User!]! 70 | pageInfo: PageInfo! 71 | } 72 | 73 | type BlogPostConnection { 74 | nodes: [BlogPost!]! 75 | pageInfo: PageInfo! 76 | } 77 | 78 | type CommentConnection { 79 | nodes: [Comment!]! 80 | pageInfo: PageInfo! 81 | } 82 | 83 | input UpdateUserInput { 84 | id: String! 85 | name: String! 86 | role: UserRole! 87 | } 88 | 89 | input CreateUserInput { 90 | name: String! 91 | password: String! 92 | role: UserRole! 93 | blogPost: CreateBlogPostInput! 94 | } 95 | 96 | union SearchResult = User | BlogPost | Comment 97 | 98 | input PageInfoInput { 99 | limit: Int 100 | offset: Int 101 | } 102 | 103 | input CreateBlogPostInput { 104 | title: String! 105 | content: String! 106 | author: CreateUserInput 107 | } 108 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/GraphQLToKarate.CommandLine.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | True 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | all 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | 36 | 37 | 38 | 39 | all 40 | runtime; build; native; contentfiles; analyzers; buildtransitive 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/AdjacencyGraphExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Extensions; 3 | using NUnit.Framework; 4 | using QuikGraph; 5 | 6 | namespace GraphQLToKarate.Tests.Extensions; 7 | 8 | [TestFixture] 9 | internal sealed class AdjacencyGraphExtensionsTests 10 | { 11 | [TestCaseSource(nameof(IsCyclicTestCases))] 12 | public void IsCyclicGraph_should_return_expected_result(AdjacencyGraph> graph, bool expectedResult) => 13 | graph.IsCyclicGraph().Should().Be(expectedResult); 14 | 15 | private static IEnumerable IsCyclicTestCases 16 | { 17 | get 18 | { 19 | yield return new TestCaseData( 20 | new AdjacencyGraph>(), 21 | false 22 | ).SetName("Empty graph should be non-cyclic"); 23 | 24 | yield return new TestCaseData( 25 | CreateSingleVertexGraph(), 26 | false 27 | ).SetName("Single vertex graph should be non-cyclic"); 28 | 29 | yield return new TestCaseData( 30 | CreateCyclicGraph(), 31 | true 32 | ).SetName("Cyclic graph should be cyclic"); 33 | 34 | yield return new TestCaseData( 35 | CreateNonCyclicGraph(), 36 | false 37 | ).SetName("Non-cyclic graph should be non-cyclic"); 38 | } 39 | } 40 | 41 | private static AdjacencyGraph> CreateSingleVertexGraph() 42 | { 43 | var graph = new AdjacencyGraph>(true); 44 | 45 | graph.AddVertex(1); 46 | 47 | return graph; 48 | } 49 | 50 | private static AdjacencyGraph> CreateCyclicGraph() 51 | { 52 | var graph = new AdjacencyGraph>(true); 53 | 54 | graph.AddVerticesAndEdgeRange(new[] 55 | { 56 | new Edge(1, 2), 57 | new Edge(2, 3), 58 | new Edge(3, 1) 59 | }); 60 | 61 | return graph; 62 | } 63 | 64 | private static AdjacencyGraph> CreateNonCyclicGraph() 65 | { 66 | var graph = new AdjacencyGraph>(true); 67 | 68 | graph.AddVerticesAndEdgeRange(new[] 69 | { 70 | new Edge(1, 2), 71 | new Edge(2, 3), 72 | new Edge(3, 4) 73 | }); 74 | 75 | return graph; 76 | } 77 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Infrastructure/StringToSetConverterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Infrastructure; 3 | using NUnit.Framework; 4 | using System.ComponentModel; 5 | using System.Globalization; 6 | 7 | namespace GraphQLToKarate.CommandLine.Tests.Infrastructure; 8 | 9 | [TestFixture] 10 | internal sealed class StringToSetConverterTests 11 | { 12 | private TypeConverter? _subjectUnderTest; 13 | 14 | [SetUp] 15 | public void SetUp() => _subjectUnderTest = new StringToSetConverter(); 16 | 17 | [Test] 18 | public void ConvertFrom_should_return_expected_ISet_of_strings_when_given_valid_input() 19 | { 20 | // arrange 21 | const string input = " Foo, Bar, Baz "; 22 | 23 | // act 24 | var result = _subjectUnderTest!.ConvertFrom(null, CultureInfo.InvariantCulture, input); 25 | 26 | // assert 27 | result.Should() 28 | .BeEquivalentTo(new HashSet(new[] { "Foo", "Bar", "Baz" }, StringComparer.OrdinalIgnoreCase)); 29 | } 30 | 31 | [Test] 32 | public void ConvertFrom_should_return_empty_ISet_of_strings_when_given_empty_input() 33 | { 34 | // arrange 35 | var input = string.Empty; 36 | 37 | // act 38 | var result = _subjectUnderTest!.ConvertFrom(null, CultureInfo.InvariantCulture, input); 39 | 40 | // assert 41 | result.Should() 42 | .BeEquivalentTo(new HashSet(StringComparer.OrdinalIgnoreCase)); 43 | } 44 | 45 | [Test] 46 | public void ConvertFrom_should_not_return_set_containing_empty_strings_when_they_are_in_input() 47 | { 48 | // arrange 49 | const string input = " Foo, , Bar, Baz "; 50 | 51 | // act 52 | var result = _subjectUnderTest!.ConvertFrom(null, CultureInfo.InvariantCulture, input); 53 | 54 | // assert 55 | result.Should() 56 | .BeEquivalentTo(new HashSet(new[] { "Foo", "Bar", "Baz" }, StringComparer.OrdinalIgnoreCase)); 57 | } 58 | 59 | [Test] 60 | public void ConvertFrom_should_throw_NotSupportedException_when_given_non_string_input() 61 | { 62 | // arrange + act 63 | var act = () => _subjectUnderTest!.ConvertFrom(null, CultureInfo.InvariantCulture, 123); 64 | 65 | // assert 66 | act.Should() 67 | .Throw() 68 | .WithMessage("Can't convert type filter value to type filter."); 69 | } 70 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Builders/GraphQLToKarateConverterBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Builders; 3 | using GraphQLToKarate.Library.Converters; 4 | using GraphQLToKarate.Library.Mappings; 5 | using GraphQLToKarate.Library.Parsers; 6 | using Microsoft.Extensions.Logging; 7 | using NSubstitute; 8 | using NUnit.Framework; 9 | 10 | namespace GraphQLToKarate.Tests.Builders; 11 | 12 | [TestFixture] 13 | internal sealed class GraphQLToKarateConverterBuilderTests 14 | { 15 | private ILogger? _mockLogger; 16 | private IGraphQLCyclicToAcyclicConverter? _mockGraphQLCyclicToAcyclicConverter; 17 | private IGraphQLSchemaParser? _mockGraphQLSchemaParser; 18 | 19 | [SetUp] 20 | public void SetUp() 21 | { 22 | _mockLogger = Substitute.For>(); 23 | _mockGraphQLCyclicToAcyclicConverter = Substitute.For(); 24 | _mockGraphQLSchemaParser = Substitute.For(); 25 | } 26 | 27 | [Test] 28 | [TestCase(true)] 29 | [TestCase(false)] 30 | public void Build_builds_expected_converter(bool populateCustomScalarMapping) 31 | { 32 | // arrange 33 | var customScalarMapping = new CustomScalarMapping( 34 | populateCustomScalarMapping 35 | ? new Dictionary { { "hello", "world" } } 36 | : new Dictionary() 37 | ); 38 | 39 | var subjectUnderTest = new GraphQLToKarateConverterBuilder( 40 | _mockLogger!, 41 | _mockGraphQLCyclicToAcyclicConverter!, 42 | _mockGraphQLSchemaParser! 43 | ); 44 | 45 | // act 46 | var graphQLToKarateConverter = subjectUnderTest 47 | .Configure() 48 | .WithCustomScalarMapping(customScalarMapping) 49 | .WithBaseUrl("https://www.builder-test.com/graphql") 50 | .WithExcludeQueriesSetting(false) 51 | .WithIncludeMutationsSetting(false) 52 | .WithQueryName("Hello") 53 | .WithMutationName("World") 54 | .WithTypeFilter(new HashSet { "Test" }) 55 | .WithQueryOperationFilter(new HashSet { "todos" }) 56 | .WithMutationOperationFilter(new HashSet { "todos" }) 57 | .Build(); 58 | 59 | // assert 60 | graphQLToKarateConverter.Should().BeOfType(); 61 | } 62 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/GraphQLTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Exceptions; 3 | 4 | namespace GraphQLToKarate.Library.Extensions; 5 | 6 | internal static class GraphQLTypeExtensions 7 | { 8 | /// 9 | /// Retrieve the associated type name contained by the given . 10 | /// 11 | /// The to retrieve the type name from. 12 | /// The type name contained by the given . 13 | public static string GetUnwrappedTypeName(this GraphQLType graphQLType) 14 | { 15 | while (true) 16 | { 17 | switch (graphQLType) 18 | { 19 | case GraphQLNonNullType graphQLNonNullType: 20 | graphQLType = graphQLNonNullType.Type; 21 | continue; 22 | case GraphQLListType graphQLListType: 23 | graphQLType = graphQLListType.Type; 24 | continue; 25 | case GraphQLNamedType namedType: 26 | return namedType.NameValue(); 27 | default: 28 | throw new InvalidGraphQLTypeException(); 29 | } 30 | } 31 | } 32 | 33 | /// 34 | /// Convenience method for accessing named node string values. 35 | /// 36 | /// The node to retrieve the name value of 37 | /// The string value of the node's name 38 | public static string NameValue(this INamedNode namedNode) => namedNode.Name.StringValue; 39 | 40 | /// 41 | /// Return whether the given GraphQL type is a list type. 42 | /// 43 | /// The source GraphQL type to check. 44 | /// Whether the given GraphQL type is a list type. 45 | public static bool IsListType(this GraphQLType graphQLType) => 46 | graphQLType is GraphQLListType or GraphQLNonNullType { Type: GraphQLListType }; 47 | 48 | /// 49 | /// Return whether the given GraphQL type is a non-null type. 50 | /// 51 | /// The source GraphQL type to check. 52 | /// Whether the given GraphQL type is a non-null type. 53 | public static bool IsNullType(this GraphQLType graphQLType) => graphQLType is not GraphQLNonNullType; 54 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Mappings/CustomScalarMappingTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Mappings; 3 | using NUnit.Framework; 4 | 5 | namespace GraphQLToKarate.Tests.Mappings; 6 | 7 | [TestFixture] 8 | public class CustomScalarMappingTests 9 | { 10 | [TestCase("String", "string")] 11 | [TestCase("Int", "int")] 12 | [TestCase("Boolean", "boolean")] 13 | public void TryGetKarateType_should_return_true_and_karate_type_when_mapping_exists( 14 | string graphQLType, 15 | string expectedKarateType) 16 | { 17 | // arrange 18 | var customScalarMapping = new CustomScalarMapping( 19 | new Dictionary 20 | { 21 | { "String", "string" }, 22 | { "Int", "int" }, 23 | { "Boolean", "boolean" } 24 | } 25 | ); 26 | 27 | // act 28 | var result = customScalarMapping.TryGetKarateType(graphQLType, out var karateType); 29 | 30 | // assert 31 | customScalarMapping.Any().Should().BeTrue(); 32 | result.Should().BeTrue(); 33 | karateType.Should().Be(expectedKarateType); 34 | } 35 | 36 | [TestCase("Float")] 37 | [TestCase("ID")] 38 | public void TryGetKarateType_should_return_false_and_null_karate_type_when_mapping_does_not_exist( 39 | string graphQLType) 40 | { 41 | // arrange 42 | var customScalarMapping = new CustomScalarMapping( 43 | new Dictionary 44 | { 45 | { "String", "string" }, 46 | { "Int", "int" }, 47 | { "Boolean", "boolean" } 48 | } 49 | ); 50 | 51 | // act 52 | var result = customScalarMapping.TryGetKarateType(graphQLType, out var karateType); 53 | 54 | // assert 55 | customScalarMapping.Any().Should().BeTrue(); 56 | result.Should().BeFalse(); 57 | karateType.Should().BeNull(); 58 | } 59 | 60 | [Test] 61 | public void ToString_generates_expected_string() 62 | { 63 | // arrange 64 | var customScalarMapping = new CustomScalarMapping( 65 | new Dictionary 66 | { 67 | { "String", "string" }, 68 | { "Int", "int" }, 69 | { "Boolean", "boolean" } 70 | } 71 | ); 72 | 73 | // act 74 | var result = customScalarMapping.ToString(); 75 | 76 | // assert 77 | result.Should().Be("String:string,Int:int,Boolean:boolean"); 78 | } 79 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Features/KarateFeatureBuilder.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Adapters; 2 | using GraphQLToKarate.Library.Extensions; 3 | using GraphQLToKarate.Library.Settings; 4 | using GraphQLToKarate.Library.Tokens; 5 | using GraphQLToKarate.Library.Types; 6 | using System.Text; 7 | 8 | namespace GraphQLToKarate.Library.Features; 9 | 10 | /// 11 | public sealed class KarateFeatureBuilder( 12 | IKarateScenarioBuilder karateScenarioBuilder, 13 | KarateFeatureBuilderSettings karateFeatureBuilderSettings) 14 | : IKarateFeatureBuilder 15 | { 16 | public string Build( 17 | IEnumerable karateObjects, 18 | IEnumerable graphQLOperations, 19 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 20 | { 21 | var lines = new List 22 | { 23 | "Feature: Test GraphQL Endpoint with Karate", 24 | string.Empty, 25 | "Background: Base URL and Schemas", 26 | $"* url {karateFeatureBuilderSettings.BaseUrl}".Indent(Indent.Single), 27 | string.Empty 28 | }; 29 | 30 | lines.AddRange(BuildKarateObjects(karateObjects)); 31 | 32 | if (!karateFeatureBuilderSettings.ExcludeQueries) 33 | { 34 | lines.AddRange(BuildGraphQLOperations(graphQLOperations, graphQLDocumentAdapter)); 35 | } 36 | 37 | var stringBuilder = new StringBuilder(); 38 | stringBuilder.AppendJoin(Environment.NewLine, lines); 39 | 40 | return stringBuilder.ToString().Trim(); 41 | } 42 | 43 | private static IEnumerable BuildKarateObjects(IEnumerable karateObjects) 44 | { 45 | foreach (var karateObject in karateObjects) 46 | { 47 | yield return $"* def {karateObject.Name.FirstCharToLower()}Schema =".Indent(Indent.Single); 48 | yield return SchemaToken.TripleQuote.Indent(Indent.Double); 49 | yield return karateObject.ToString().Indent(Indent.Triple); 50 | yield return SchemaToken.TripleQuote.Indent(Indent.Double); 51 | yield return string.Empty; 52 | } 53 | } 54 | 55 | private IEnumerable BuildGraphQLOperations( 56 | IEnumerable graphQLOperations, 57 | IGraphQLDocumentAdapter graphQLDocumentAdapter) 58 | { 59 | foreach (var graphQLQueryField in graphQLOperations) 60 | { 61 | yield return karateScenarioBuilder.Build(graphQLQueryField, graphQLDocumentAdapter); 62 | yield return string.Empty; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Infrastructure/HostConfigurator.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Mappers; 2 | using GraphQLToKarate.CommandLine.Prompts; 3 | using GraphQLToKarate.CommandLine.Settings; 4 | using GraphQLToKarate.Library.Builders; 5 | using GraphQLToKarate.Library.Converters; 6 | using GraphQLToKarate.Library.Mappings; 7 | using GraphQLToKarate.Library.Parsers; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Serilog; 11 | using Serilog.Sinks.SystemConsole.Themes; 12 | using System.IO.Abstractions; 13 | 14 | namespace GraphQLToKarate.CommandLine.Infrastructure; 15 | 16 | /// 17 | /// Configures the host builder. 18 | /// 19 | internal static class HostConfigurator 20 | { 21 | /// 22 | /// Configures the host builder. 23 | /// 24 | /// The command line arguments. 25 | /// The configured host builder. 26 | public static IHostBuilder ConfigureHostBuilder(string[]? args) => Host 27 | .CreateDefaultBuilder(args) 28 | .ConfigureServices(serviceCollection => 29 | { 30 | serviceCollection.AddSingleton(); 31 | serviceCollection.AddSingleton(); 32 | serviceCollection.AddTransient(); 33 | serviceCollection.AddSingleton(); 34 | serviceCollection.AddSingleton(); 35 | serviceCollection.AddTransient(); 36 | serviceCollection.AddSingleton(); 37 | serviceCollection.AddSingleton(); 38 | serviceCollection.AddSingleton(); 39 | serviceCollection.AddSingleton(); 40 | serviceCollection.AddSingleton(); 41 | }) 42 | .UseSerilog((_, loggerConfiguration) => 43 | loggerConfiguration 44 | .MinimumLevel.ControlledBy(LogCommandSettingsInterceptor.LoggingLevelSwitch) 45 | .WriteTo.Console( 46 | outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message:lj}{NewLine}{Exception}", 47 | theme: AnsiConsoleTheme.Sixteen 48 | ) 49 | ); 50 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # parantheses formatting 4 | dotnet_diagnostic.SA1000.severity = none 5 | dotnet_diagnostic.SA1009.severity = none 6 | dotnet_diagnostic.SA1111.severity = none 7 | 8 | # add trailing commas 9 | dotnet_diagnostic.MA0007.severity = none 10 | 11 | # format provider is missing 12 | dotnet_diagnostic.MA0011.severity = none 13 | 14 | # specify the parameter name in ArgumentException 15 | dotnet_diagnostic.MA0015.severity = none 16 | 17 | # closing square bracket must be followed by a space 18 | dotnet_diagnostic.SA1011.severity = none 19 | 20 | # prefix calls to an instance member with 'this' 21 | dotnet_diagnostic.SA1101.severity = none 22 | 23 | # A constructor should not follow a property 24 | dotnet_diagnostic.SA1201.severity = none 25 | 26 | # Static members should not follow non-static members 27 | dotnet_diagnostic.SA1204.severity = none 28 | 29 | # The 'required' modifier should appear before 'public' 30 | dotnet_diagnostic.SA1206.severity = none 31 | 32 | # field names must not begin with underscores 33 | dotnet_diagnostic.SA1309.severity = none 34 | 35 | # related to SA1309, field names must begin with a lowercase letter 36 | dotnet_diagnostic.SA1312.severity = none 37 | 38 | # the last statement in a multi-line initializer or list must end with a trailing comma 39 | dotnet_diagnostic.SA1413.severity = none 40 | 41 | # elements should not be on the same line 42 | dotnet_diagnostic.SA1502.severity = none 43 | 44 | # require XML documentation 45 | dotnet_diagnostic.CS1591.severity = none 46 | 47 | # require XML documentation 48 | dotnet_diagnostic.SA1600.severity = none 49 | 50 | # require emum item documentation 51 | dotnet_diagnostic.SA1602.severity = none 52 | 53 | # documentation for properties must begin with the accessor keywords on the property 54 | dotnet_diagnostic.SA1623.severity = none 55 | 56 | # all XML documentation sections must end with a period/full stop 57 | dotnet_diagnostic.SA1629.severity = none 58 | 59 | # every C# code file must have a standard file header 60 | dotnet_diagnostic.SA1633.severity = none 61 | 62 | # require standardized summary test for constructors 63 | dotnet_diagnostic.SA1642.severity = none 64 | 65 | # max method length 66 | MA0051.maximum_lines_per_method = 120 67 | 68 | [*Tests.cs] 69 | # parameter spans multiple lines - not helpful for block strings 70 | dotnet_diagnostic.SA1118.severity = none 71 | 72 | # ordering rules - disabling these since it's nice to group test data 73 | dotnet_diagnostic.SA1203.severity = none 74 | 75 | # opening square brackets should not be preceded by a space 76 | dotnet_diagnostic.SA1010.severity = none 77 | 78 | # closing brace must be followed by a blank line 79 | dotnet_diagnostic.SA1513.severity = none -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQLToKarate.Library.Extensions; 2 | 3 | /// 4 | /// Extension methods on the type. 5 | /// 6 | internal static class StringExtensions 7 | { 8 | /// 9 | /// Convert the first character of the given to lowercase. 10 | /// 11 | /// The source to manipulate. 12 | /// The with its first character converted to lowercase. 13 | public static string FirstCharToLower(this string source) 14 | { 15 | if (string.IsNullOrEmpty(source)) 16 | { 17 | return string.Empty; 18 | } 19 | 20 | return string.Create(source.Length, source, static (characters, str) => 21 | { 22 | characters[0] = char.ToLowerInvariant(str[0]); 23 | str.AsSpan(1).CopyTo(characters[1..]); 24 | }); 25 | } 26 | 27 | /// 28 | /// Convert the first character of the given to uppercase. 29 | /// 30 | /// The source to manipulate. 31 | /// The with its first character converted to uppercase. 32 | public static string FirstCharToUpper(this string source) 33 | { 34 | if (string.IsNullOrEmpty(source)) 35 | { 36 | return string.Empty; 37 | } 38 | 39 | return string.Create(source.Length, source, static (characters, str) => 40 | { 41 | characters[0] = char.ToUpperInvariant(str[0]); 42 | str.AsSpan(1).CopyTo(characters[1..]); 43 | }); 44 | } 45 | 46 | /// 47 | /// Indents a string (including multi-line strings) by the specified indent amount. 48 | /// 49 | /// This is different from in that it properly indents strings that span multiple lines. 50 | /// 51 | /// The string to manipulate. 52 | /// The amount to indent the string. 53 | /// The source string, indented by the specified amount. 54 | public static string Indent(this string source, int indent) 55 | { 56 | var indentation = new string(' ', indent); 57 | 58 | return indentation + source.Replace(Environment.NewLine, Environment.NewLine + indentation, StringComparison.OrdinalIgnoreCase); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Commands/ConvertCommand.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Prompts; 2 | using GraphQLToKarate.CommandLine.Settings; 3 | using GraphQLToKarate.Library.Builders; 4 | using Microsoft.Extensions.Logging; 5 | using Spectre.Console.Cli; 6 | using System.IO.Abstractions; 7 | 8 | namespace GraphQLToKarate.CommandLine.Commands; 9 | 10 | internal sealed class ConvertCommand( 11 | IFileSystem fileSystem, 12 | IConvertCommandSettingsLoader convertCommandSettingsLoader, 13 | IGraphQLToKarateConverterBuilder graphQLToKarateConverterBuilder, 14 | IConvertCommandSettingsPrompt convertCommandSettingsPrompt, 15 | ILogger logger) 16 | : AsyncCommand 17 | { 18 | public override async Task ExecuteAsync(CommandContext context, ConvertCommandSettings commandSettings) 19 | { 20 | var loadedCommandSettings = await convertCommandSettingsLoader.LoadAsync(commandSettings).ConfigureAwait(false); 21 | 22 | if (!commandSettings.IsNonInteractive) 23 | { 24 | loadedCommandSettings = await convertCommandSettingsPrompt.PromptAsync(loadedCommandSettings).ConfigureAwait(false); 25 | } 26 | 27 | logger.LogInformation("Running GraphQL to Karate conversion..."); 28 | 29 | var graphQLToKarateConverter = graphQLToKarateConverterBuilder 30 | .Configure() 31 | .WithBaseUrl(loadedCommandSettings.BaseUrl) 32 | .WithCustomScalarMapping(loadedCommandSettings.CustomScalarMapping) 33 | .WithExcludeQueriesSetting(loadedCommandSettings.ExcludeQueries) 34 | .WithIncludeMutationsSetting(loadedCommandSettings.IncludeMutations) 35 | .WithQueryName(loadedCommandSettings.QueryName) 36 | .WithMutationName(loadedCommandSettings.MutationName) 37 | .WithTypeFilter(loadedCommandSettings.TypeFilter) 38 | .WithQueryOperationFilter(loadedCommandSettings.QueryOperationFilter) 39 | .WithMutationOperationFilter(loadedCommandSettings.MutationOperationFilter) 40 | .Build(); 41 | 42 | var karateFeature = graphQLToKarateConverter.Convert(loadedCommandSettings.GraphQLSchema); 43 | 44 | await WriteKarateFeatureAsync(loadedCommandSettings.OutputFile, karateFeature).ConfigureAwait(false); 45 | 46 | logger.LogInformation("Conversion complete! View the output at {outputFile}.", loadedCommandSettings.OutputFile); 47 | 48 | return 0; 49 | } 50 | 51 | private async Task WriteKarateFeatureAsync(string outputFile, string karateFeature) 52 | { 53 | var file = fileSystem.FileInfo.New(outputFile); 54 | 55 | file.Directory!.Create(); 56 | 57 | await fileSystem.File.WriteAllTextAsync(outputFile, karateFeature).ConfigureAwait(false); 58 | } 59 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | Thanks for your interest in contributing to `graphql-to-karate`! Here are a few general guidelines on contributing and 4 | reporting bugs that we ask you to review. Following these guidelines helps to communicate that you respect the time of 5 | the contributors managing and developing this open source project. In return, they should reciprocate that respect in 6 | addressing your issue, assessing changes, and helping you finalize your pull requests. In that spirit of mutual respect, 7 | we endeavour to review incoming issues and pull requests within 10 days, and will close any lingering issues or pull 8 | requests after 60 days of inactivity. 9 | 10 | Please note that all of your interactions in the project are subject to our [Code of Conduct](CODE_OF_CONDUCT.md). This 11 | includes creation of issues or pull requests, commenting on issues or pull requests, and extends to all interactions in 12 | any real-time space (eg. Slack, Discord, etc). 13 | 14 | ## Reporting Issues 15 | 16 | Before reporting a new issue, please ensure that the issue was not already reported or fixed by searching through our 17 | [issues list](https://github.com/wbaldoumas/graphql-to-karate/issues). 18 | 19 | When creating a new issue, please be sure to include a **title and clear description**, as much relevant information as 20 | possible, and, if possible, a test case. 21 | 22 | **If you discover a security bug, please do not report it through GitHub. Instead, please see security procedures in 23 | [SECURITY.md](SECURITY.md).** 24 | 25 | ## Sending Pull Requests 26 | 27 | Before sending a new pull request, take a look at existing pull requests and issues to see if the proposed change or fix 28 | has been discussed in the past, or if the change was already implemented but not yet released. 29 | 30 | We expect new pull requests to include tests for any affected behavior, and, as we follow semantic versioning, we may 31 | reserve breaking changes until the next major version release. 32 | 33 | ## Other Ways to Contribute 34 | 35 | We welcome anyone that wants to contribute to `graphql-to-karate` to triage and reply to open issues to help troubleshoot 36 | and fix existing bugs. Here is what you can do: 37 | 38 | - Help ensure that existing issues follows the recommendations from the _[Reporting Issues](#reporting-issues)_ section, 39 | providing feedback to the issue's author on what might be missing. 40 | - Review and update the existing content of our [Wiki](https://github.com/wbaldoumas/graphql-to-karate/wiki) with up-to-date 41 | instructions and code samples. 42 | - Review existing pull requests, and testing patches against real existing applications that use `graphql-to-karate`. 43 | - Write a test, or add a missing test case to an existing test. 44 | 45 | Thanks again for your interest on contributing to `graphql-to-karate`! 46 | 47 | :heart: 48 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Mappers/GraphQLToKarateUserConfigurationMapperTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Mappers; 3 | using GraphQLToKarate.CommandLine.Settings; 4 | using GraphQLToKarate.Library.Mappings; 5 | using NUnit.Framework; 6 | 7 | namespace GraphQLToKarate.CommandLine.Tests.Mappers; 8 | 9 | public class GraphQLToKarateUserConfigurationMapperTests 10 | { 11 | [Test] 12 | public void Map_should_map_to_expected_LoadedConvertCommandSettings() 13 | { 14 | // Arrange 15 | var graphQLToKarateUserConfigurationMapper = new GraphQLToKarateUserConfigurationMapper(); 16 | 17 | var graphQLToKarateUserConfiguration = new GraphQLToKarateUserConfiguration 18 | { 19 | GraphQLSchema = "SampleSchema", 20 | InputFile = "SampleInputFile", 21 | OutputFile = "SampleOutputFile", 22 | BaseUrl = "https://example.com", 23 | QueryName = "SampleQuery", 24 | MutationName = "SampleMutation", 25 | ExcludeQueries = true, 26 | CustomScalarMapping = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 27 | IncludeMutations = true, 28 | QueryOperationFilter = new HashSet { "queryOperation1", "queryOperation2" }, 29 | MutationOperationFilter = new HashSet { "mutationOperation1", "mutationOperation2" }, 30 | TypeFilter = new HashSet { "type1", "type2" } 31 | }; 32 | 33 | var expectedLoadedConvertCommandSettings = new LoadedConvertCommandSettings 34 | { 35 | GraphQLSchema = graphQLToKarateUserConfiguration.GraphQLSchema, 36 | InputFile = graphQLToKarateUserConfiguration.InputFile, 37 | OutputFile = graphQLToKarateUserConfiguration.OutputFile, 38 | BaseUrl = graphQLToKarateUserConfiguration.BaseUrl, 39 | QueryName = graphQLToKarateUserConfiguration.QueryName, 40 | MutationName = graphQLToKarateUserConfiguration.MutationName, 41 | ExcludeQueries = graphQLToKarateUserConfiguration.ExcludeQueries, 42 | CustomScalarMapping = new CustomScalarMapping(graphQLToKarateUserConfiguration.CustomScalarMapping), 43 | IncludeMutations = graphQLToKarateUserConfiguration.IncludeMutations, 44 | QueryOperationFilter = graphQLToKarateUserConfiguration.QueryOperationFilter, 45 | MutationOperationFilter = graphQLToKarateUserConfiguration.MutationOperationFilter, 46 | TypeFilter = graphQLToKarateUserConfiguration.TypeFilter 47 | }; 48 | 49 | // act 50 | var loadedConvertCommandSettings = graphQLToKarateUserConfigurationMapper.Map(graphQLToKarateUserConfiguration); 51 | 52 | // assert 53 | loadedConvertCommandSettings.Should().BeEquivalentTo(expectedLoadedConvertCommandSettings); 54 | } 55 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/GraphQLInputValueDefinitionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLParser.AST; 3 | using GraphQLToKarate.Library.Apollo; 4 | using GraphQLToKarate.Library.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace GraphQLToKarate.Tests.Extensions; 8 | 9 | [TestFixture] 10 | public class GraphQLInputValueDefinitionExtensionsTests 11 | { 12 | [Test] 13 | public void IsInaccessible_returns_false_when_directives_is_null() 14 | { 15 | // arrange 16 | var inputValueDefinition = new GraphQLInputValueDefinition( 17 | new GraphQLName("foo"), 18 | new GraphQLNamedType(new GraphQLName("bar")) 19 | ); 20 | 21 | // act 22 | var result = inputValueDefinition.IsInaccessible(); 23 | 24 | // assert 25 | result.Should().BeFalse(); 26 | } 27 | 28 | [Test] 29 | [TestCase(Directives.Inaccessible, true)] 30 | [TestCase(Directives.External, false)] 31 | [TestCase("someDirective", false)] 32 | public void IsInaccessible_returns_expected_result(string directiveName, bool expected) 33 | { 34 | // arrange 35 | var inputValueDefinition = new GraphQLInputValueDefinition( 36 | new GraphQLName("foo"), 37 | new GraphQLNamedType(new GraphQLName("bar")) 38 | ) 39 | { 40 | Directives = new GraphQLDirectives( 41 | [new GraphQLDirective(new GraphQLName(directiveName))] 42 | ) 43 | }; 44 | 45 | // act 46 | var result = inputValueDefinition.IsInaccessible(); 47 | 48 | // assert 49 | result.Should().Be(expected); 50 | } 51 | 52 | [Test] 53 | public void IsExternal_returns_false_when_directives_is_null() 54 | { 55 | // arrange 56 | var inputValueDefinition = new GraphQLInputValueDefinition( 57 | new GraphQLName("foo"), 58 | new GraphQLNamedType(new GraphQLName("bar")) 59 | ); 60 | 61 | // act 62 | var result = inputValueDefinition.IsExternal(); 63 | 64 | // assert 65 | result.Should().BeFalse(); 66 | } 67 | 68 | [Test] 69 | [TestCase(Directives.Inaccessible, false)] 70 | [TestCase(Directives.External, true)] 71 | [TestCase("someDirective", false)] 72 | public void IsExternal_returns_expected_result(string directiveName, bool expected) 73 | { 74 | // arrange 75 | var inputValueDefinition = new GraphQLInputValueDefinition( 76 | new GraphQLName("foo"), 77 | new GraphQLNamedType(new GraphQLName("bar")) 78 | ) 79 | { 80 | Directives = new GraphQLDirectives([new GraphQLDirective(new GraphQLName(directiveName))]) 81 | }; 82 | 83 | // act 84 | var result = inputValueDefinition.IsExternal(); 85 | 86 | // assert 87 | result.Should().Be(expected); 88 | } 89 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/GraphQLFieldDefinitionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLParser.AST; 3 | using GraphQLToKarate.Library.Apollo; 4 | using GraphQLToKarate.Library.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace GraphQLToKarate.Tests.Extensions; 8 | 9 | [TestFixture] 10 | public class GraphQLFieldDefinitionExtensionsTests 11 | { 12 | [Test] 13 | [TestCase(Directives.Inaccessible, true)] 14 | [TestCase(Directives.External, false)] 15 | [TestCase("someDirective", false)] 16 | public void IsInaccessible_returns_expected_result(string directiveName, bool expected) 17 | { 18 | // arrange 19 | var fieldDefinition = new GraphQLFieldDefinition( 20 | new GraphQLName("someName"), 21 | new GraphQLNamedType(new GraphQLName("someType")) 22 | ) 23 | { 24 | Directives = new GraphQLDirectives( 25 | [new GraphQLDirective(new GraphQLName(directiveName))] 26 | ) 27 | }; 28 | 29 | // act 30 | var result = fieldDefinition.IsInaccessible(); 31 | 32 | // assert 33 | result.Should().Be(expected); 34 | } 35 | 36 | [Test] 37 | [TestCase(Directives.External, true)] 38 | [TestCase(Directives.Inaccessible, false)] 39 | [TestCase("someDirective", false)] 40 | public void IsExternal_returns_expected_result(string directiveName, bool expected) 41 | { 42 | // arrange 43 | var fieldDefinition = new GraphQLFieldDefinition( 44 | new GraphQLName("someName"), 45 | new GraphQLNamedType(new GraphQLName("someType")) 46 | ) 47 | { 48 | Directives = new GraphQLDirectives( 49 | [new GraphQLDirective(new GraphQLName(directiveName))] 50 | ) 51 | }; 52 | 53 | // act 54 | var result = fieldDefinition.IsExternal(); 55 | 56 | // assert 57 | result.Should().Be(expected); 58 | } 59 | 60 | [Test] 61 | public void IsInaccessible_with_null_directives_returns_false() 62 | { 63 | // arrange 64 | var fieldDefinition = new GraphQLFieldDefinition( 65 | new GraphQLName("someName"), 66 | new GraphQLNamedType(new GraphQLName("someType")) 67 | ); 68 | 69 | // act 70 | var result = fieldDefinition.IsInaccessible(); 71 | 72 | // assert 73 | result.Should().BeFalse(); 74 | } 75 | 76 | [Test] 77 | public void IsExternal_with_null_directives_returns_false() 78 | { 79 | // arrange 80 | var fieldDefinition = new GraphQLFieldDefinition( 81 | new GraphQLName("someName"), 82 | new GraphQLNamedType(new GraphQLName("someType")) 83 | ); 84 | 85 | // act 86 | var result = fieldDefinition.IsExternal(); 87 | 88 | // assert 89 | result.Should().BeFalse(); 90 | } 91 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Types/KarateObjectTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.Library.Tokens; 3 | using GraphQLToKarate.Library.Types; 4 | using NUnit.Framework; 5 | 6 | namespace GraphQLToKarate.Tests.Types; 7 | 8 | [TestFixture] 9 | internal sealed class KarateObjectTests 10 | { 11 | [Test] 12 | [TestCaseSource(nameof(TestCases))] 13 | public void Schema(KarateObject karateObject, string expectedName, string expectedSchema) 14 | { 15 | karateObject.Name.Should().Be(expectedName); 16 | karateObject.ToString().Should().Be(expectedSchema); 17 | } 18 | 19 | private static IEnumerable TestCases 20 | { 21 | get 22 | { 23 | const string testKarateObjectName = "test"; 24 | 25 | yield return new TestCaseData( 26 | new KarateObject( 27 | testKarateObjectName, 28 | new List 29 | { 30 | new KarateNonNullType( 31 | new KarateListType( 32 | new KarateNullType( 33 | new KarateType(KarateToken.String, "names") 34 | ) 35 | ) 36 | ) 37 | } 38 | ), 39 | testKarateObjectName, 40 | """ 41 | { 42 | names: '#[] ##string' 43 | } 44 | """ 45 | ).SetName("Single field is handled as expected."); 46 | 47 | yield return new TestCaseData( 48 | new KarateObject( 49 | testKarateObjectName, 50 | new List 51 | { 52 | new KarateNonNullType(new KarateType(KarateToken.Number, "id")), 53 | new KarateNonNullType(new KarateType(KarateToken.String, "name")), 54 | new KarateNonNullType(new KarateType(KarateToken.Boolean, "isFriendly")), 55 | new KarateNullType(new KarateType(KarateToken.Object, "foo")), 56 | new KarateNonNullType( 57 | new KarateListType( 58 | new KarateNonNullType( 59 | new KarateType("friendSchema", "friends") 60 | ) 61 | ) 62 | ) 63 | } 64 | ), 65 | testKarateObjectName, 66 | """ 67 | { 68 | id: '#number', 69 | name: '#string', 70 | isFriendly: '#boolean', 71 | foo: '##object', 72 | friends: '#[] #friendSchema' 73 | } 74 | """ 75 | ).SetName("Multiple fields are handled as expected."); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLScalarToExampleValueConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Exceptions; 4 | using GraphQLToKarate.Library.Extensions; 5 | using GraphQLToKarate.Library.Mappings; 6 | using GraphQLToKarate.Library.Tokens; 7 | 8 | namespace GraphQLToKarate.Library.Converters; 9 | 10 | /// 11 | internal sealed class GraphQLScalarToExampleValueConverter(ICustomScalarMapping customScalarMapping) : IGraphQLScalarToExampleValueConverter 12 | { 13 | private const int MinRandomIntValue = 100; 14 | 15 | private const int MaxRandomIntValue = 1000; 16 | 17 | private const double MinRandomFloatValue = 100.0; 18 | 19 | private const double MaxRandomFloatValue = 1000.0; 20 | 21 | private readonly Random _random = new(); 22 | 23 | public string Convert(GraphQLType graphQLType, IGraphQLDocumentAdapter graphQLDocumentAdapter) => graphQLType.GetUnwrappedTypeName() switch 24 | { 25 | GraphQLToken.Id => GenerateRandomString(), 26 | GraphQLToken.String => GenerateRandomString(), 27 | GraphQLToken.Int => GenerateRandomInt(), 28 | GraphQLToken.Float => GenerateRandomFloat(), 29 | GraphQLToken.Boolean => GenerateRandomBoolean(), 30 | { } graphQLTypeName when graphQLDocumentAdapter.IsGraphQLEnumTypeDefinition(graphQLTypeName) => GenerateRandomEnumValue(graphQLTypeName, graphQLDocumentAdapter), 31 | { } graphQLTypeName when customScalarMapping.TryGetKarateType(graphQLTypeName, out var karateType) => GenerateRandomValueFromKarateType(karateType), 32 | _ => "" 33 | }; 34 | 35 | private static string GenerateRandomString() => $"\"{Guid.NewGuid():N}\""; 36 | 37 | private string GenerateRandomInt() => _random.Next(MinRandomIntValue, MaxRandomIntValue).ToString(); 38 | 39 | private string GenerateRandomFloat() => $"{(_random.NextDouble() * (MaxRandomFloatValue - MinRandomFloatValue)) + MinRandomFloatValue:N2}"; 40 | 41 | private string GenerateRandomBoolean() => _random.Next(0, 1) == 0 ? "true" : "false"; 42 | 43 | private string GenerateRandomEnumValue(string graphQLTypeName, IGraphQLDocumentAdapter graphQLDocumentAdapter) 44 | { 45 | var graphQLEnumTypeDefinition = graphQLDocumentAdapter.GetGraphQLEnumTypeDefinition(graphQLTypeName); 46 | 47 | if (graphQLEnumTypeDefinition?.Values is null) 48 | { 49 | throw new InvalidGraphQLTypeException(); 50 | } 51 | 52 | var index = _random.Next(0, graphQLEnumTypeDefinition.Values.Count); 53 | 54 | return graphQLEnumTypeDefinition.Values[index].NameValue(); 55 | } 56 | 57 | private string GenerateRandomValueFromKarateType(string karateType) => karateType switch 58 | { 59 | KarateToken.String => GenerateRandomString(), 60 | KarateToken.Number => GenerateRandomInt(), 61 | KarateToken.Boolean => GenerateRandomBoolean(), 62 | _ => "" 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/GraphQLEnumValueDefinitionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLParser.AST; 3 | using GraphQLToKarate.Library.Apollo; 4 | using GraphQLToKarate.Library.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace GraphQLToKarate.Tests.Extensions; 8 | 9 | [TestFixture] 10 | public class GraphQLEnumValueDefinitionExtensionsTests 11 | { 12 | [Test] 13 | [TestCase(Directives.Inaccessible, true)] 14 | [TestCase(Directives.External, false)] 15 | [TestCase("someDirective", false)] 16 | public void IsInaccessible_returns_expected_result(string directiveName, bool expected) 17 | { 18 | // arrange 19 | var graphQLEnumValueDefinition = new GraphQLEnumValueDefinition( 20 | new GraphQLName("someName"), 21 | new GraphQLEnumValue(new GraphQLName("someValue")) 22 | ) 23 | { 24 | Directives = new GraphQLDirectives([new GraphQLDirective(new GraphQLName(directiveName))]) 25 | }; 26 | 27 | // act 28 | var result = graphQLEnumValueDefinition.IsInaccessible(); 29 | 30 | // assert 31 | result.Should().Be(expected); 32 | } 33 | 34 | [Test] 35 | [TestCase(Directives.Inaccessible, false)] 36 | [TestCase(Directives.External, true)] 37 | [TestCase("someDirective", false)] 38 | public void IsExternal_returns_expected_result(string directiveName, bool expected) 39 | { 40 | // arrange 41 | var graphQLEnumValueDefinition = new GraphQLEnumValueDefinition( 42 | new GraphQLName("someName"), 43 | new GraphQLEnumValue(new GraphQLName("someValue")) 44 | ) 45 | { 46 | Directives = new GraphQLDirectives([new GraphQLDirective(new GraphQLName(directiveName))]) 47 | }; 48 | 49 | // act 50 | var result = graphQLEnumValueDefinition.IsExternal(); 51 | 52 | // assert 53 | result.Should().Be(expected); 54 | } 55 | 56 | [Test] 57 | public void IsInaccessible_returns_false_for_null_directives() 58 | { 59 | // arrange 60 | var graphQLEnumValueDefinition = new GraphQLEnumValueDefinition( 61 | new GraphQLName("someName"), 62 | new GraphQLEnumValue(new GraphQLName("someValue")) 63 | ) 64 | { 65 | Directives = null 66 | }; 67 | 68 | // act 69 | var result = graphQLEnumValueDefinition.IsInaccessible(); 70 | 71 | // assert 72 | result.Should().BeFalse(); 73 | } 74 | 75 | [Test] 76 | public void IsExternal_returns_false_for_null_directives() 77 | { 78 | // arrange 79 | var graphQLEnumValueDefinition = new GraphQLEnumValueDefinition( 80 | new GraphQLName("someName"), 81 | new GraphQLEnumValue(new GraphQLName("someValue")) 82 | ) 83 | { 84 | Directives = null 85 | }; 86 | 87 | // act 88 | var result = graphQLEnumValueDefinition.IsExternal(); 89 | 90 | // assert 91 | result.Should().BeFalse(); 92 | } 93 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Mappings/CustomScalarMappingLoader.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Tokens; 2 | using System.IO.Abstractions; 3 | using System.Text.Json; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace GraphQLToKarate.Library.Mappings; 7 | 8 | /// 9 | public sealed class CustomScalarMappingLoader(IFile file) : ICustomScalarMappingLoader 10 | { 11 | private readonly Regex _regex = new(@"^([\w\s]+:[\w\s]+(?:,\s*|$))*[\w\s]+:[\w\s]+(?:,\s*)?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture, TimeSpan.FromSeconds(1)); 12 | 13 | public async Task LoadAsync(string? customScalarMappingSource) => customScalarMappingSource switch 14 | { 15 | null => new CustomScalarMapping(), 16 | _ when IsTextLoadable(customScalarMappingSource) => LoadFromText(customScalarMappingSource), 17 | _ when IsFileLoadable(customScalarMappingSource) => await LoadFromFileAsync(customScalarMappingSource).ConfigureAwait(false), 18 | _ => new CustomScalarMapping() 19 | }; 20 | 21 | public bool IsValid(string? customScalarMappingSource) => 22 | string.IsNullOrEmpty(customScalarMappingSource) || 23 | IsTextLoadable(customScalarMappingSource) || 24 | IsFileLoadable(customScalarMappingSource); 25 | 26 | public bool IsFileLoadable(string filePath) 27 | { 28 | if (!file.Exists(filePath)) 29 | { 30 | return false; 31 | } 32 | 33 | var fileContent = file.ReadAllText(filePath); 34 | 35 | if (string.IsNullOrWhiteSpace(fileContent)) 36 | { 37 | return true; 38 | } 39 | 40 | if (_regex.IsMatch(fileContent)) 41 | { 42 | return true; 43 | } 44 | 45 | try 46 | { 47 | _ = JsonSerializer.Deserialize>(fileContent); 48 | } 49 | catch (JsonException) 50 | { 51 | return false; 52 | } 53 | 54 | return true; 55 | } 56 | 57 | public bool IsTextLoadable(string text) => _regex.IsMatch(text); 58 | 59 | public async Task LoadFromFileAsync(string filePath) => 60 | DeserializeFileContent(await file.ReadAllTextAsync(filePath).ConfigureAwait(false)); 61 | 62 | public ICustomScalarMapping LoadFromText(string text) => new CustomScalarMapping( 63 | text.Split(SchemaToken.Comma, StringSplitOptions.TrimEntries) 64 | .Select(customScalarMappingEntry => customScalarMappingEntry.Split(SchemaToken.Colon, StringSplitOptions.TrimEntries)) 65 | .ToDictionary( 66 | customScalarMappingEntryParts => customScalarMappingEntryParts.First(), 67 | customScalarMappingEntryParts => customScalarMappingEntryParts.Last(), 68 | StringComparer.OrdinalIgnoreCase 69 | ) 70 | ); 71 | 72 | private ICustomScalarMapping DeserializeFileContent(string fileContent) => IsTextLoadable(fileContent) 73 | ? LoadFromText(fileContent) 74 | : new CustomScalarMapping(JsonSerializer.Deserialize>(fileContent)!); 75 | } -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '19 10 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Use only 'java' to analyze code written in Java, Kotlin or both 38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both 39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v6 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v4 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | 54 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 55 | # queries: security-extended,security-and-quality 56 | 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@v4 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 65 | 66 | # If the Autobuild fails above, remove it and uncomment the following three lines. 67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 68 | 69 | # - run: | 70 | # echo "Run, Build Application using script" 71 | # ./location_of_script_within_repo/buildscript.sh 72 | 73 | - name: Perform CodeQL Analysis 74 | uses: github/codeql-action/analyze@v4 75 | with: 76 | category: "/language:${{matrix.language}}" 77 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQLToKarate.Library", "GraphQLToKarate.Library\GraphQLToKarate.Library.csproj", "{36FC5206-F8D9-40F3-9099-D5407352DF9E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQLToKarate.Tests", "..\tests\GraphQLToKarate.Tests\GraphQLToKarate.Tests.csproj", "{A1A010CB-9A80-478D-8959-56C54B6DF10B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQLToKarate.CommandLine", "GraphQLToKarate.CommandLine\GraphQLToKarate.CommandLine.csproj", "{8FC55E2E-E71A-43BE-B240-695F4A5384D2}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQLToKarate.CommandLine.Tests", "..\tests\GraphQLToKarate.CommandLine.Tests\GraphQLToKarate.CommandLine.Tests.csproj", "{E7FE4B1C-96E2-4EC2-B803-C5A77BCBC5A8}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQLToKarate.Integration.Api", "..\tests\GraphQLToKarate.Integration.Api\GraphQLToKarate.Integration.Api.csproj", "{15B64BF3-6F2C-47F3-A809-1B2207D1B640}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {36FC5206-F8D9-40F3-9099-D5407352DF9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {36FC5206-F8D9-40F3-9099-D5407352DF9E}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {36FC5206-F8D9-40F3-9099-D5407352DF9E}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {36FC5206-F8D9-40F3-9099-D5407352DF9E}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {A1A010CB-9A80-478D-8959-56C54B6DF10B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {A1A010CB-9A80-478D-8959-56C54B6DF10B}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {A1A010CB-9A80-478D-8959-56C54B6DF10B}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {A1A010CB-9A80-478D-8959-56C54B6DF10B}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {8FC55E2E-E71A-43BE-B240-695F4A5384D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {8FC55E2E-E71A-43BE-B240-695F4A5384D2}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {8FC55E2E-E71A-43BE-B240-695F4A5384D2}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {8FC55E2E-E71A-43BE-B240-695F4A5384D2}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {E7FE4B1C-96E2-4EC2-B803-C5A77BCBC5A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {E7FE4B1C-96E2-4EC2-B803-C5A77BCBC5A8}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {E7FE4B1C-96E2-4EC2-B803-C5A77BCBC5A8}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {E7FE4B1C-96E2-4EC2-B803-C5A77BCBC5A8}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {15B64BF3-6F2C-47F3-A809-1B2207D1B640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {15B64BF3-6F2C-47F3-A809-1B2207D1B640}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {15B64BF3-6F2C-47F3-A809-1B2207D1B640}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {15B64BF3-6F2C-47F3-A809-1B2207D1B640}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {A9FC539C-54D3-4301-A9EC-AE9FB2DD3054} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Settings/LoadedConvertCommandSettings.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.Library.Mappings; 2 | using System.Text; 3 | 4 | namespace GraphQLToKarate.CommandLine.Settings; 5 | 6 | internal sealed class LoadedConvertCommandSettings 7 | { 8 | public required string GraphQLSchema { get; init; } 9 | 10 | public required string InputFile { get; init; } 11 | 12 | public required string OutputFile { get; init; } 13 | 14 | public required string QueryName { get; init; } 15 | 16 | public required string MutationName { get; init; } 17 | 18 | public required bool ExcludeQueries { get; init; } 19 | 20 | public required bool IncludeMutations { get; init; } 21 | 22 | public required string BaseUrl { get; init; } 23 | 24 | public required ICustomScalarMapping CustomScalarMapping { get; init; } 25 | 26 | public required ISet QueryOperationFilter { get; init; } 27 | 28 | public required ISet MutationOperationFilter { get; init; } 29 | 30 | public required ISet TypeFilter { get; init; } 31 | 32 | public string ToCommandLineCommand() 33 | { 34 | var stringBuilder = new StringBuilder(); 35 | 36 | stringBuilder.Append($"graphql-to-karate convert {InputFile} --non-interactive "); 37 | 38 | if (!string.Equals(OutputFile, Options.OutputFileOptionDefaultValue)) 39 | { 40 | stringBuilder.Append($"{Options.OutputFileOptionName} {OutputFile} "); 41 | } 42 | 43 | if (!string.Equals(QueryName, Options.QueryNameOptionDefaultValue)) 44 | { 45 | stringBuilder.Append($"{Options.QueryNameOptionName} {QueryName} "); 46 | } 47 | 48 | if (!string.Equals(MutationName, Options.MutationNameOptionDefaultValue)) 49 | { 50 | stringBuilder.Append($"{Options.MutationNameOptionName} {MutationName} "); 51 | } 52 | 53 | if (ExcludeQueries) 54 | { 55 | stringBuilder.Append($"{Options.ExcludeQueriesOptionName} "); 56 | } 57 | 58 | if (IncludeMutations) 59 | { 60 | stringBuilder.Append($"{Options.IncludeMutationsOptionName} "); 61 | } 62 | 63 | if (!string.Equals(BaseUrl, Options.BaseUrlOptionDefaultValue)) 64 | { 65 | var baseUrl = BaseUrl; 66 | 67 | // handle escaping quotes 68 | if (BaseUrl.StartsWith('\"') && BaseUrl.EndsWith('\"')) 69 | { 70 | baseUrl = $"\\{string.Concat(baseUrl[..^1], '\\', baseUrl[^1])}"; 71 | } 72 | 73 | stringBuilder.Append($"{Options.BaseUrlOptionName} {baseUrl} "); 74 | } 75 | 76 | if (CustomScalarMapping.Any()) 77 | { 78 | stringBuilder.Append($"{Options.CustomScalarMappingOptionName} {CustomScalarMapping.ToString()} "); 79 | } 80 | 81 | if (QueryOperationFilter.Any()) 82 | { 83 | stringBuilder.Append($"{Options.QueryOperationFilterOptionName} {string.Join(",", QueryOperationFilter)} "); 84 | } 85 | 86 | if (MutationOperationFilter.Any()) 87 | { 88 | stringBuilder.Append($"{Options.MutationOperationFilterOptionName} {string.Join(",", MutationOperationFilter)} "); 89 | } 90 | 91 | if (TypeFilter.Any()) 92 | { 93 | stringBuilder.Append($"{Options.TypeFilterOptionName} {string.Join(",", TypeFilter)} "); 94 | } 95 | 96 | return stringBuilder.ToString().TrimEnd(); 97 | } 98 | } -------------------------------------------------------------------------------- /configuration/schema/v1/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "title": "JSON schema for graphql-to-karate (https://github.com/wbaldoumas/graphql-to-karate)", 5 | "description": "A JSON schema for the graphql-to-karate configuration file", 6 | "properties": { 7 | "outputFile": { 8 | "type": "string", 9 | "title": "Output File", 10 | "description": "The output file to write the Karate feature to", 11 | "default": "graphql.feature" 12 | }, 13 | "queryName": { 14 | "type": "string", 15 | "title": "Query Name", 16 | "description": "The name of the GraphQL query type", 17 | "default": "Query" 18 | }, 19 | "mutationName": { 20 | "type": "string", 21 | "title": "Mutation Name", 22 | "description": "The name of the GraphQL mutation type", 23 | "default": "Mutation" 24 | }, 25 | "excludeQueries": { 26 | "type": "boolean", 27 | "title": "Exclude Queries", 28 | "description": "Whether to exclude queries from the Karate feature or not", 29 | "default": false 30 | }, 31 | "includeMutations": { 32 | "type": "boolean", 33 | "title": "Include Mutations", 34 | "description": "Whether to include mutations in the Karate feature or not", 35 | "default": false 36 | }, 37 | "baseUrl": { 38 | "type": "string", 39 | "title": "Base URL", 40 | "description": "The base URL to be used in the Karate feature", 41 | "default": "\"https://your-awesome-api.com\"" 42 | }, 43 | "customScalarMapping": { 44 | "type": "object", 45 | "title": "Custom Scalar Mapping", 46 | "description": "A mapping of custom scalar types to their equivalent Karate types", 47 | "default": {}, 48 | "additionalProperties": { 49 | "type": "string" 50 | }, 51 | "examples": [ 52 | { 53 | "DateTime": "string", 54 | "Long": "number", 55 | "BigDecimal": "number", 56 | "UUID": "string" 57 | } 58 | ] 59 | }, 60 | "typeFilter": { 61 | "type": "array", 62 | "title": "Type Filter", 63 | "description": "A list of types to include in the Karate feature. If empty, all types will be included", 64 | "default": [], 65 | "items": [ 66 | { 67 | "type": "string" 68 | } 69 | ] 70 | }, 71 | "queryOperationFilter": { 72 | "type": "array", 73 | "title": "Query Operation Filter", 74 | "description": "A list of query operations to include in the Karate feature. If empty, all query operations will be included", 75 | "default": [], 76 | "items": [ 77 | { 78 | "type": "string" 79 | } 80 | ] 81 | }, 82 | "mutationOperationFilter": { 83 | "type": "array", 84 | "title": "Mutation Operation Filter", 85 | "description": "A list of mutation operations to include in the Karate feature. If empty, all mutation operations will be included", 86 | "default": [], 87 | "items": [ 88 | { 89 | "type": "string" 90 | } 91 | ] 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/GraphQLToKarate.CommandLine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | William Baldoumas 5 | Exe 6 | 0.2.0 7 | net8.0 8 | enable 9 | enable 10 | true 11 | graphql-to-karate 12 | graphql-to-karate 13 | A tool for converting GraphQL schemas to Karate API tests. 14 | true 15 | true 16 | git 17 | https://github.com/wbaldoumas/graphql-to-karate 18 | https://github.com/wbaldoumas/graphql-to-karate 19 | graphql;karate;test-automation;testing;test;api-test;api-testing;karate-framework;testing-tools;graphql-server;graphql-schema 20 | README.md 21 | LICENSE 22 | Copyright ©2024 William Baldoumas 23 | graphql-to-karate 24 | icon.jpg 25 | icon.ico 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | 39 | all 40 | runtime; build; native; contentfiles; analyzers; buildtransitive 41 | 42 | 43 | 44 | 45 | 46 | all 47 | runtime; build; native; contentfiles; analyzers; buildtransitive 48 | 49 | 50 | 51 | all 52 | runtime; build; native; contentfiles; analyzers; buildtransitive 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | True 69 | \ 70 | 71 | 72 | True 73 | \ 74 | 75 | 76 | True 77 | \ 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/GraphQLToKarate.CommandLine/Settings/ConvertCommandSettingsLoader.cs: -------------------------------------------------------------------------------- 1 | using GraphQLToKarate.CommandLine.Exceptions; 2 | using GraphQLToKarate.CommandLine.Mappers; 3 | using GraphQLToKarate.Library.Mappings; 4 | using Microsoft.Extensions.Logging; 5 | using System.IO.Abstractions; 6 | using System.Text.Json; 7 | 8 | namespace GraphQLToKarate.CommandLine.Settings; 9 | 10 | /// 11 | internal sealed class ConvertCommandSettingsLoader( 12 | IFile file, 13 | ICustomScalarMappingLoader customScalarMappingLoader, 14 | IGraphQLToKarateUserConfigurationMapper graphQLToKarateUserConfigurationMapper, 15 | ILogger logger) 16 | : IConvertCommandSettingsLoader 17 | { 18 | public async Task LoadAsync(ConvertCommandSettings convertCommandSettings) 19 | { 20 | var graphQLSchema = await file.ReadAllTextAsync(convertCommandSettings.InputFile!).ConfigureAwait(false); 21 | 22 | if (!string.IsNullOrEmpty(convertCommandSettings.ConfigurationFile)) 23 | { 24 | return await LoadFromUserConfigurationFile( 25 | graphQLSchema, 26 | convertCommandSettings.InputFile!, 27 | convertCommandSettings.ConfigurationFile 28 | ).ConfigureAwait(false); 29 | } 30 | 31 | var customScalarMapping = await customScalarMappingLoader.LoadAsync( 32 | convertCommandSettings.CustomScalarMapping 33 | ).ConfigureAwait(false); 34 | 35 | return new LoadedConvertCommandSettings 36 | { 37 | GraphQLSchema = graphQLSchema, 38 | InputFile = convertCommandSettings.InputFile!, 39 | OutputFile = convertCommandSettings.OutputFile!, 40 | CustomScalarMapping = customScalarMapping, 41 | BaseUrl = convertCommandSettings.BaseUrl ?? Options.BaseUrlOptionDefaultValue, 42 | ExcludeQueries = convertCommandSettings.ExcludeQueries, 43 | IncludeMutations = convertCommandSettings.IncludeMutations, 44 | QueryName = convertCommandSettings.QueryName ?? Options.QueryNameOptionDefaultValue, 45 | MutationName = convertCommandSettings.MutationName ?? Options.MutationNameOptionDefaultValue, 46 | TypeFilter = convertCommandSettings.TypeFilter, 47 | QueryOperationFilter = convertCommandSettings.QueryOperationFilter, 48 | MutationOperationFilter = convertCommandSettings.MutationOperationFilter 49 | }; 50 | } 51 | 52 | private async Task LoadFromUserConfigurationFile( 53 | string graphQLSchema, 54 | string inputFile, 55 | string configurationFile) 56 | { 57 | try 58 | { 59 | var configurationFileContent = await file.ReadAllTextAsync(configurationFile).ConfigureAwait(false); 60 | 61 | var graphQLToKarateUserConfiguration = JsonSerializer.Deserialize( 62 | configurationFileContent, 63 | new JsonSerializerOptions 64 | { 65 | PropertyNameCaseInsensitive = true 66 | } 67 | ); 68 | 69 | if (graphQLToKarateUserConfiguration is null) 70 | { 71 | throw new InvalidOperationException("Invalid configuration file format."); 72 | } 73 | 74 | graphQLToKarateUserConfiguration.GraphQLSchema = graphQLSchema; 75 | graphQLToKarateUserConfiguration.InputFile = inputFile; 76 | 77 | return graphQLToKarateUserConfigurationMapper.Map(graphQLToKarateUserConfiguration); 78 | } 79 | catch (Exception exception) 80 | { 81 | logger.LogError(exception, GraphQLToKarateConfigurationException.DefaultMessage); 82 | 83 | throw new GraphQLToKarateConfigurationException( 84 | GraphQLToKarateConfigurationException.DefaultMessage, 85 | exception 86 | ); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Extensions/GraphQLTypeExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLParser.AST; 3 | using GraphQLToKarate.Library.Exceptions; 4 | using GraphQLToKarate.Library.Extensions; 5 | using GraphQLToKarate.Tests.Mocks; 6 | using NUnit.Framework; 7 | 8 | namespace GraphQLToKarate.Tests.Extensions; 9 | 10 | [TestFixture] 11 | internal sealed class GraphQLTypeExtensionsTests 12 | { 13 | [TestCaseSource(nameof(TestCases))] 14 | public void GetUnwrappedTypeName_should_return_correct_type_name( 15 | GraphQLType graphQLType, 16 | string expectedTypeName 17 | ) => graphQLType.GetUnwrappedTypeName().Should().Be(expectedTypeName); 18 | 19 | private static IEnumerable TestCases 20 | { 21 | get 22 | { 23 | yield return new TestCaseData( 24 | new GraphQLNamedType(new GraphQLName("NamedType")), 25 | "NamedType" 26 | ) 27 | .SetName("GraphQLNamedType input"); 28 | 29 | yield return new TestCaseData( 30 | new GraphQLNonNullType(new GraphQLNamedType(new GraphQLName("NonNullType"))), 31 | "NonNullType" 32 | ) 33 | .SetName("GraphQLNonNullType input"); 34 | 35 | yield return new TestCaseData( 36 | new GraphQLListType(new GraphQLNonNullType(new GraphQLNamedType(new GraphQLName("ListType")))), 37 | "ListType" 38 | ) 39 | .SetName("GraphQLListType input"); 40 | } 41 | } 42 | 43 | [Test] 44 | public void GetUnwrappedTypeName_should_throw_an_exception_when_unsupported_graphql_type_is_encountered() 45 | { 46 | // arrange 47 | var graphQLType = new UnsupportedGraphQLType(); 48 | 49 | // act 50 | var act = () => graphQLType.GetUnwrappedTypeName(); 51 | 52 | // assert 53 | act.Should().ThrowExactly(); 54 | } 55 | 56 | [Test] 57 | public void NameValue_should_return_correct_name_value() 58 | { 59 | // arrange 60 | var namedNode = new GraphQLNamedType(new GraphQLName("NamedType")); 61 | 62 | // act 63 | var result = namedNode.NameValue(); 64 | 65 | // assert 66 | result.Should().Be("NamedType"); 67 | } 68 | 69 | [Test] 70 | [TestCaseSource(nameof(IsListTypeTestCases))] 71 | public void IsListType_returns_expected_value(GraphQLType graphQLType, bool expectedIsListType) => 72 | graphQLType.IsListType().Should().Be(expectedIsListType); 73 | 74 | private static IEnumerable IsListTypeTestCases 75 | { 76 | get 77 | { 78 | var graphQLName = new GraphQLName("foo"); 79 | 80 | yield return new TestCaseData( 81 | new GraphQLNamedType(graphQLName), 82 | false 83 | ); 84 | 85 | yield return new TestCaseData( 86 | new GraphQLNonNullType(new GraphQLNamedType(graphQLName)), 87 | false 88 | ); 89 | 90 | yield return new TestCaseData( 91 | new GraphQLListType(new GraphQLNamedType(graphQLName)), 92 | true 93 | ); 94 | 95 | yield return new TestCaseData( 96 | new GraphQLNonNullType(new GraphQLListType(new GraphQLNamedType(graphQLName))), 97 | true 98 | ); 99 | } 100 | } 101 | 102 | [Test] 103 | [TestCaseSource(nameof(IsNullTypeTestCases))] 104 | public void IsNullType_returns_expected_value(GraphQLType graphQLType, bool expectedIsNullType) => 105 | graphQLType.IsNullType().Should().Be(expectedIsNullType); 106 | 107 | private static IEnumerable IsNullTypeTestCases 108 | { 109 | get 110 | { 111 | var graphQLName = new GraphQLName("foo"); 112 | 113 | yield return new TestCaseData( 114 | new GraphQLNamedType(graphQLName), 115 | true 116 | ); 117 | 118 | yield return new TestCaseData( 119 | new GraphQLNonNullType(new GraphQLNamedType(graphQLName)), 120 | false 121 | ); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Adapters/IGraphQLDocumentAdapter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace GraphQLToKarate.Library.Adapters; 4 | 5 | /// 6 | /// Provides ergonomic access to the GraphQL document. 7 | /// 8 | public interface IGraphQLDocumentAdapter 9 | { 10 | GraphQLObjectTypeDefinition? GraphQLQueryTypeDefinition { get; } 11 | 12 | GraphQLObjectTypeDefinition? GraphQLMutationTypeDefinition { get; } 13 | 14 | IEnumerable GraphQLObjectTypeDefinitions { get; } 15 | 16 | IEnumerable GraphQLInterfaceTypeDefinitions { get; } 17 | 18 | /// 19 | /// Is the given a ? 20 | /// 21 | /// The name of the GraphQL type definition to check. 22 | /// Whether the given GraphQL type definition is a 23 | bool IsGraphQLEnumTypeDefinition(string graphQLTypeDefinitionName); 24 | 25 | /// 26 | /// Is the given a ? 27 | /// 28 | /// The name of the GraphQL type definition to check. 29 | /// Whether the given GraphQL type definition is a 30 | bool IsGraphQLTypeDefinitionWithFields(string graphQLTypeDefinitionName); 31 | 32 | /// 33 | /// Is the given a ? 34 | /// 35 | /// The name of the GraphQL type definition to check. 36 | /// Whether the given GraphQL type definition is a . 37 | bool IsGraphQLUnionTypeDefinition(string graphQLTypeDefinitionName); 38 | 39 | /// 40 | /// Is the given a ? 41 | /// 42 | /// The name of the GraphQL type definition to check. 43 | /// Whether the given GraphQL type definition is a . 44 | bool IsGraphQLInputObjectTypeDefinition(string graphQLTypeDefinitionName); 45 | 46 | /// 47 | /// Retrieve the given as a . 48 | /// 49 | /// The GraphQL type definition to retrieve. 50 | /// The or null if one doesn't exist. 51 | IHasFieldsDefinitionNode? GetGraphQLTypeDefinitionWithFields(string graphQLTypeDefinitionName); 52 | 53 | /// 54 | /// Retrieve the given as a . 55 | /// 56 | /// The GraphQL type definition to retrieve. 57 | /// The or null if one doesn't exist. 58 | GraphQLUnionTypeDefinition? GetGraphQLUnionTypeDefinition(string graphQLTypeDefinitionName); 59 | 60 | /// 61 | /// Retrieve the given as a . 62 | /// 63 | /// The GraphQL type definition to retrieve. 64 | /// The or null if one doesn't exist. 65 | GraphQLEnumTypeDefinition? GetGraphQLEnumTypeDefinition(string graphQLTypeDefinitionName); 66 | 67 | /// 68 | /// Retrieve the given as a . 69 | /// 70 | /// The GraphQL type definition to retrieve. 71 | /// The or null if one doesn't exist. 72 | GraphQLInputObjectTypeDefinition? GetGraphQLInputObjectTypeDefinition(string graphQLTypeDefinitionName); 73 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.Tests/Converters/GraphQLCustomScalarTypeConverterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLParser.AST; 3 | using GraphQLToKarate.Library.Adapters; 4 | using GraphQLToKarate.Library.Converters; 5 | using GraphQLToKarate.Library.Extensions; 6 | using GraphQLToKarate.Library.Mappings; 7 | using GraphQLToKarate.Library.Tokens; 8 | using GraphQLToKarate.Library.Types; 9 | using NSubstitute; 10 | using NSubstitute.ExceptionExtensions; 11 | using NUnit.Framework; 12 | 13 | namespace GraphQLToKarate.Tests.Converters; 14 | 15 | [TestFixture] 16 | internal sealed class GraphQLCustomScalarTypeConverterTests 17 | { 18 | private IGraphQLTypeConverter? _mockGraphQLTypeConverter; 19 | private ICustomScalarMapping? _customScalarMapping; 20 | private IGraphQLTypeConverter? _subjectUnderTest; 21 | 22 | private const string CustomScalarNameLong = "Long"; 23 | private const string CustomScalarNameTime = "Time"; 24 | 25 | [SetUp] 26 | public void SetUp() 27 | { 28 | _mockGraphQLTypeConverter = Substitute.For(); 29 | 30 | _customScalarMapping = new CustomScalarMapping( 31 | new Dictionary 32 | { 33 | { CustomScalarNameLong, KarateToken.Number }, 34 | { CustomScalarNameTime, KarateToken.String } 35 | } 36 | ); 37 | 38 | _subjectUnderTest = new GraphQLCustomScalarTypeConverter( 39 | _customScalarMapping!, 40 | _mockGraphQLTypeConverter! 41 | ); 42 | } 43 | 44 | [Test] 45 | [TestCaseSource(nameof(TestCases))] 46 | public void Convert( 47 | string graphQLFieldName, 48 | GraphQLType graphQLType, 49 | IGraphQLDocumentAdapter graphQLDocumentAdapter, 50 | KarateTypeBase expectedKarateType) 51 | { 52 | // arrange 53 | var shouldCallUnderlyingGraphQLTypeConverter = !_customScalarMapping!.TryGetKarateType(graphQLType.GetUnwrappedTypeName(), out _); 54 | 55 | if (shouldCallUnderlyingGraphQLTypeConverter) 56 | { 57 | _mockGraphQLTypeConverter! 58 | .Convert(graphQLFieldName, graphQLType, graphQLDocumentAdapter) 59 | .Returns(expectedKarateType); 60 | } 61 | else 62 | { 63 | _mockGraphQLTypeConverter! 64 | .Convert(graphQLFieldName, graphQLType, graphQLDocumentAdapter) 65 | .Throws(new InvalidOperationException()); 66 | } 67 | 68 | var karateType = _subjectUnderTest!.Convert( 69 | graphQLFieldName, 70 | graphQLType, 71 | graphQLDocumentAdapter 72 | ); 73 | 74 | // assert 75 | karateType.Should().BeEquivalentTo(expectedKarateType); 76 | 77 | if (shouldCallUnderlyingGraphQLTypeConverter) 78 | { 79 | _mockGraphQLTypeConverter! 80 | .Received() 81 | .Convert(graphQLFieldName, graphQLType, graphQLDocumentAdapter); 82 | } 83 | else 84 | { 85 | _mockGraphQLTypeConverter! 86 | .DidNotReceiveWithAnyArgs() 87 | .Convert(Arg.Any(), Arg.Any(), Arg.Any()); 88 | } 89 | } 90 | 91 | private static IEnumerable TestCases 92 | { 93 | get 94 | { 95 | const string testFieldName = "Test"; 96 | 97 | var emptyGraphQLDocumentAdapter = new GraphQLDocumentAdapter(new GraphQLDocument([])); 98 | 99 | yield return new TestCaseData( 100 | testFieldName, 101 | new GraphQLNamedType(new GraphQLName(CustomScalarNameLong)), 102 | emptyGraphQLDocumentAdapter, 103 | new KarateType(KarateToken.Number, testFieldName) 104 | ).SetName("Custom scalar present in the mapping returns expected mapped Karate type."); 105 | 106 | yield return new TestCaseData( 107 | testFieldName, 108 | new GraphQLNamedType(new GraphQLName(CustomScalarNameTime)), 109 | emptyGraphQLDocumentAdapter, 110 | new KarateType(KarateToken.String, testFieldName) 111 | ).SetName("Other custom scalar present in the mapping returns expected mapped Karate type."); 112 | 113 | yield return new TestCaseData( 114 | testFieldName, 115 | new GraphQLNamedType(new GraphQLName("SomeFunnyType")), 116 | emptyGraphQLDocumentAdapter, 117 | new KarateType(KarateToken.Present, testFieldName) 118 | ).SetName("Custom type not present in the mapping returns expected Karate type from underlying GraphQLTypeConverter."); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /tests/GraphQLToKarate.CommandLine.Tests/Settings/ConvertCommandSettingsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using GraphQLToKarate.CommandLine.Settings; 3 | using GraphQLToKarate.Library.Mappings; 4 | using NSubstitute; 5 | using NUnit.Framework; 6 | using System.IO.Abstractions; 7 | 8 | namespace GraphQLToKarate.CommandLine.Tests.Settings; 9 | 10 | [TestFixture] 11 | internal sealed class ConvertCommandSettingsTests 12 | { 13 | private IFile? _mockFile; 14 | private ICustomScalarMappingValidator? _mockCustomScalarMappingValidator; 15 | 16 | [SetUp] 17 | public void SetUp() 18 | { 19 | _mockFile = Substitute.For(); 20 | _mockCustomScalarMappingValidator = Substitute.For(); 21 | } 22 | 23 | [TestCaseSource(nameof(TestCases))] 24 | public void ConvertSettings_validation_returns_expected_validation_result( 25 | string? graphQLSchemaFile, 26 | string? customScalarMapping, 27 | bool graphQLSchemaFileExistsReturn, 28 | bool customScalarMappingValidatorReturn, 29 | string? expectedMessage) 30 | { 31 | // arrange 32 | _mockFile!.Exists(graphQLSchemaFile).Returns(graphQLSchemaFileExistsReturn); 33 | 34 | _mockCustomScalarMappingValidator! 35 | .IsValid(customScalarMapping) 36 | .Returns(customScalarMappingValidatorReturn); 37 | 38 | var settings = new ConvertCommandSettings(_mockFile, _mockCustomScalarMappingValidator!) 39 | { 40 | InputFile = graphQLSchemaFile, 41 | CustomScalarMapping = customScalarMapping 42 | }; 43 | 44 | // act 45 | var result = settings.Validate(); 46 | 47 | // assert 48 | if (expectedMessage is null) 49 | { 50 | result.Successful.Should().BeTrue(); 51 | } 52 | else 53 | { 54 | result.Successful.Should().BeFalse(); 55 | result.Message.Should().Be(expectedMessage); 56 | } 57 | } 58 | 59 | private static IEnumerable TestCases 60 | { 61 | get 62 | { 63 | yield return new TestCaseData( 64 | null, 65 | "config.json", 66 | true, 67 | true, 68 | "Please provide a valid file path and filename for the GraphQL schema to convert." 69 | ).SetName("When InputFile name is null, settings are invalid."); 70 | 71 | yield return new TestCaseData( 72 | string.Empty, 73 | "config.json", 74 | true, 75 | true, 76 | "Please provide a valid file path and filename for the GraphQL schema to convert." 77 | ).SetName("When InputFile name is empty, settings are invalid."); 78 | 79 | yield return new TestCaseData( 80 | "schema.graphql", 81 | "config.json", 82 | false, 83 | true, 84 | "GraphQL schema file does not exist. Please provide a valid file path and filename for the GraphQL schema to convert." 85 | ).SetName("When non-null and non-empty InputFile name points to file that doesn't exist, settings are invalid."); 86 | 87 | yield return new TestCaseData( 88 | "schema.graphql", 89 | "customScalarMapping.json", 90 | true, 91 | false, 92 | $"The {Options.CustomScalarMappingOptionName} option value is invalid. Please provide either a valid file path or valid custom scalar mapping value." 93 | ).SetName("When non-null and non-empty custom scalar mapping filename points to file that doesn't exist, settings are invalid."); 94 | 95 | yield return new TestCaseData( 96 | "schema.graphql", 97 | null, 98 | true, 99 | true, 100 | null 101 | ).SetName("When non-null and non-empty InputFile name points to file that exists, settings are valid (null custom scalar mapping)."); 102 | 103 | yield return new TestCaseData( 104 | "schema.graphql", 105 | string.Empty, 106 | true, 107 | true, 108 | null 109 | ).SetName("When non-null and non-empty InputFile name points to file that exists, settings are valid (empty custom scalar mapping)."); 110 | 111 | yield return new TestCaseData( 112 | "schema.graphql", 113 | "customScalarMapping.json", 114 | true, 115 | true, 116 | null 117 | ).SetName("When non-null and non-empty custom scalar mapping filename points to file that exists, settings are valid."); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/GraphQLToKarate.Library/Converters/GraphQLInputValueToExampleValueConverter.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLToKarate.Library.Adapters; 3 | using GraphQLToKarate.Library.Exceptions; 4 | using GraphQLToKarate.Library.Extensions; 5 | using GraphQLToKarate.Library.Tokens; 6 | using QuikGraph; 7 | using System.Text; 8 | 9 | namespace GraphQLToKarate.Library.Converters; 10 | 11 | /// 12 | internal sealed class GraphQLInputValueToExampleValueConverter(IGraphQLScalarToExampleValueConverter graphQLScalarToExampleValueConverter) : IGraphQLInputValueToExampleValueConverter 13 | { 14 | public string Convert( 15 | GraphQLInputValueDefinition graphQLInputValueDefinition, 16 | IGraphQLDocumentAdapter graphQLDocumentAdapter 17 | ) => Convert(graphQLInputValueDefinition.Type, graphQLDocumentAdapter); 18 | 19 | private string Convert( 20 | GraphQLInputValueDefinition graphQLInputValueDefinition, 21 | IGraphQLDocumentAdapter graphQLDocumentAdapter, 22 | AdjacencyGraph> inputValueRelationships 23 | ) => Convert(graphQLInputValueDefinition.Type, graphQLDocumentAdapter, inputValueRelationships); 24 | 25 | private string Convert( 26 | GraphQLType graphQLType, 27 | IGraphQLDocumentAdapter graphQLDocumentAdapter, 28 | AdjacencyGraph>? inputValueRelationships = null) => graphQLType switch 29 | { 30 | GraphQLListType graphQLListType => $"[ {Convert(graphQLListType.Type, graphQLDocumentAdapter, inputValueRelationships)} ]", 31 | GraphQLNonNullType graphQLNonNullType => Convert(graphQLNonNullType.Type, graphQLDocumentAdapter, inputValueRelationships), 32 | GraphQLNamedType graphQLNamedType when 33 | graphQLDocumentAdapter.IsGraphQLInputObjectTypeDefinition(graphQLNamedType.GetUnwrappedTypeName()) => Convert( 34 | graphQLDocumentAdapter.GetGraphQLInputObjectTypeDefinition(graphQLNamedType.GetUnwrappedTypeName())!, 35 | graphQLDocumentAdapter, 36 | inputValueRelationships ?? new AdjacencyGraph>() 37 | ), 38 | GraphQLNamedType graphQLNamedType => graphQLScalarToExampleValueConverter.Convert( 39 | graphQLNamedType, 40 | graphQLDocumentAdapter 41 | ), 42 | _ => throw new InvalidGraphQLTypeException() 43 | }; 44 | 45 | private string Convert( 46 | GraphQLInputObjectTypeDefinition graphQLInputObjectTypeDefinition, 47 | IGraphQLDocumentAdapter graphQLDocumentAdapter, 48 | AdjacencyGraph> inputValueRelationships) 49 | { 50 | var stringBuilder = new StringBuilder(); 51 | 52 | stringBuilder.Append($"{SchemaToken.OpenBrace} "); 53 | 54 | var parentInputValueDefinitionTypeName = graphQLInputObjectTypeDefinition.NameValue(); 55 | 56 | inputValueRelationships.AddVertex(parentInputValueDefinitionTypeName); 57 | 58 | foreach (var graphQLInputValueDefinition in graphQLInputObjectTypeDefinition.Fields!) 59 | { 60 | var childInputValueName = graphQLInputValueDefinition.NameValue(); 61 | var childInputValueDefinitionTypeName = graphQLInputValueDefinition.Type.GetUnwrappedTypeName(); 62 | 63 | inputValueRelationships.AddVertex(childInputValueDefinitionTypeName); 64 | 65 | var edge = new Edge(parentInputValueDefinitionTypeName, childInputValueDefinitionTypeName); 66 | 67 | inputValueRelationships.AddEdge(edge); 68 | 69 | // If adding the edge generates a cycle, remove it and generate a placeholder value to prevent infinite recursion... 70 | if (inputValueRelationships.IsCyclicGraph()) 71 | { 72 | inputValueRelationships.RemoveEdge(edge); 73 | 74 | if (graphQLInputValueDefinition.Type.IsNullType()) 75 | { 76 | // if the type is nullable, just skip it... 77 | continue; 78 | } 79 | 80 | // if the type is non-nullable, generate a placeholder value... 81 | stringBuilder.Append( 82 | graphQLInputValueDefinition.Type.IsListType() 83 | ? $"\"{childInputValueName}\": [ ]{SchemaToken.Comma} " 84 | : $"\"{childInputValueName}\": {SchemaToken.Comma} " 85 | ); 86 | 87 | continue; 88 | } 89 | 90 | stringBuilder.Append($"\"{childInputValueName}\": {Convert(graphQLInputValueDefinition, graphQLDocumentAdapter, inputValueRelationships)}{SchemaToken.Comma} "); 91 | } 92 | 93 | stringBuilder.TrimEnd(2); // Remove the trailing comma and space. 94 | stringBuilder.Append($" {SchemaToken.CloseBrace}"); 95 | 96 | return stringBuilder.ToString(); 97 | } 98 | } --------------------------------------------------------------------------------