Skip to content

Commit dcbb0ef

Browse files
ocdiTrevor Nicholseddeee888
authored
Support output of System.Text.Json attributes (#1160)
* Support System.Text.Json in C# Operations * Update .changeset/tall-beans-drum.md Co-authored-by: Eddy Nguyen <[email protected]> --------- Co-authored-by: Trevor Nichols <[email protected]> Co-authored-by: Eddy Nguyen <[email protected]>
1 parent 7e5cf4d commit dcbb0ef

File tree

9 files changed

+158
-74
lines changed

9 files changed

+158
-74
lines changed

.changeset/tall-beans-drum.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@graphql-codegen/c-sharp-operations': minor
3+
'@graphql-codegen/c-sharp-common': minor
4+
'@graphql-codegen/c-sharp': patch
5+
---
6+
7+
Support output of System.Text.Json attributes
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './c-sharp-declaration-block.js';
2-
export * from './scalars.js';
3-
export * from './utils.js';
42
export * from './c-sharp-field-types.js';
3+
export * from './json-attributes.js';
54
export * from './keywords.js';
65
export * from './member-naming.js';
6+
export * from './scalars.js';
7+
export * from './utils.js';

packages/plugins/c-sharp/c-sharp/src/json-attributes.ts renamed to packages/plugins/c-sharp/c-sharp-common/src/json-attributes.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,50 @@ function unsupportedSource(attributesSource: JsonAttributesSource): void {
44
throw new Error(`Unsupported JSON attributes source: ${attributesSource}`);
55
}
66

7+
type JsonAttributeEnumConfiguration = {
8+
decorator: string;
9+
enumMemberAttribute: (value: string) => string;
10+
};
11+
712
export class JsonAttributesSourceConfiguration {
813
readonly namespace: string;
914
readonly propertyAttribute: string;
1015
readonly requiredAttribute: string;
16+
readonly enumConfiguration: JsonAttributeEnumConfiguration;
1117

12-
constructor(namespace: string, propertyAttribute: string, requiredAttribute: string) {
18+
constructor(
19+
namespace: string,
20+
propertyAttribute: string,
21+
requiredAttribute: string,
22+
enumConfig: JsonAttributeEnumConfiguration,
23+
) {
1324
this.namespace = namespace;
1425
this.propertyAttribute = propertyAttribute;
1526
this.requiredAttribute = requiredAttribute;
27+
this.enumConfiguration = enumConfig;
1628
}
1729
}
1830

1931
const newtonsoftConfiguration = new JsonAttributesSourceConfiguration(
2032
'Newtonsoft.Json',
2133
'JsonProperty',
2234
'JsonRequired',
35+
{
36+
decorator:
37+
'[DataContract]\n[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]',
38+
enumMemberAttribute: value => `[EnumMember(Value = "${value}")]`,
39+
},
2340
);
2441

2542
// System.Text.Json does not have support of `JsonRequired` alternative (as for .NET 5)
2643
const systemTextJsonConfiguration = new JsonAttributesSourceConfiguration(
2744
'System.Text.Json.Serialization',
2845
'JsonPropertyName',
2946
null,
47+
{
48+
enumMemberAttribute: value => `[JsonStringEnumMemberName("${value}")]`,
49+
decorator: '[JsonConverter(typeof(JsonStringEnumConverter))]',
50+
},
3051
);
3152

3253
export function getJsonAttributeSourceConfiguration(attributesSource: JsonAttributesSource) {

packages/plugins/c-sharp/c-sharp-operations/src/config.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MemberNameConventionConfig } from '@graphql-codegen/c-sharp-common';
1+
import { JsonAttributesSource, MemberNameConventionConfig } from '@graphql-codegen/c-sharp-common';
22
import { RawClientSideBasePluginConfig } from '@graphql-codegen/visitor-plugin-common';
33

44
/**
@@ -72,4 +72,16 @@ export interface CSharpOperationsRawPluginConfig
7272
* ```
7373
*/
7474
typesafeOperation?: boolean;
75+
76+
/**
77+
* @default Newtonsoft.Json
78+
* @description Library that should be used to emit JSON attributes.
79+
*
80+
* @exampleMarkdown
81+
* ```yaml
82+
* config:
83+
* jsonAttributesSource: System.Text.Json
84+
* ```
85+
*/
86+
jsonAttributesSource?: JsonAttributesSource;
7587
}

packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ import {
2323
convertSafeName,
2424
CSharpDeclarationBlock,
2525
CSharpFieldType,
26+
getJsonAttributeSourceConfiguration,
2627
getListInnerTypeNode,
2728
getListTypeDepth,
2829
getListTypeField,
2930
getMemberNamingFunction,
3031
isValueType,
32+
JsonAttributesSourceConfiguration,
3133
MemberNamingFn,
3234
wrapFieldType,
3335
} from '@graphql-codegen/c-sharp-common';
@@ -58,6 +60,7 @@ export interface CSharpOperationsPluginConfig extends ClientSideBasePluginConfig
5860
mutationSuffix: string;
5961
subscriptionSuffix: string;
6062
typesafeOperation: boolean;
63+
jsonAttributesConfiguration: JsonAttributesSourceConfiguration;
6164
memberNamingFunction: MemberNamingFn;
6265
}
6366

@@ -92,6 +95,9 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor<
9295
querySuffix: rawConfig.querySuffix || defaultSuffix,
9396
mutationSuffix: rawConfig.mutationSuffix || defaultSuffix,
9497
subscriptionSuffix: rawConfig.subscriptionSuffix || defaultSuffix,
98+
jsonAttributesConfiguration: getJsonAttributeSourceConfiguration(
99+
rawConfig.jsonAttributesSource || 'Newtonsoft.Json',
100+
),
95101
scalars: buildScalarsFromConfig(schema, rawConfig, C_SHARP_SCALARS),
96102
typesafeOperation: rawConfig.typesafeOperation || false,
97103
memberNamingFunction: getMemberNamingFunction(rawConfig),
@@ -211,7 +217,12 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor<
211217

212218
public getCSharpImports(): string {
213219
return (
214-
['System', 'Newtonsoft.Json', 'GraphQL', 'GraphQL.Client.Abstractions']
220+
[
221+
'System',
222+
this.config.jsonAttributesConfiguration.namespace,
223+
'GraphQL',
224+
'GraphQL.Client.Abstractions',
225+
]
215226
.map(i => `using ${i};`)
216227
.join('\n') + '\n'
217228
);
@@ -339,7 +350,7 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor<
339350
);
340351
return indentMultiline(
341352
[
342-
`[JsonProperty("${node.name.value}")]`,
353+
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${node.name.value}")]`,
343354
`public ${responseTypeName} ${propertyName} { get; set; }`,
344355
].join('\n') + '\n',
345356
);
@@ -379,7 +390,7 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor<
379390
return indentMultiline(
380391
[
381392
innerClassDefinition,
382-
`[JsonProperty("${node.name.value}")]`,
393+
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${node.name.value}")]`,
383394
`public ${selectionTypeName} ${propertyName} { get; set; }`,
384395
].join('\n') + '\n',
385396
);
@@ -434,7 +445,7 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor<
434445
);
435446
return indentMultiline(
436447
[
437-
`[JsonProperty("${v.variable.name.value}")]`,
448+
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${v.variable.name.value}")]`,
438449
`public ${inputTypeName} ${propertyName} { get; set; }`,
439450
].join('\n') + '\n',
440451
);
@@ -616,7 +627,7 @@ ${this._getOperationMethod(node)}
616627
);
617628
return indentMultiline(
618629
[
619-
`[JsonProperty("${f.name.value}")]`,
630+
`[${this.config.jsonAttributesConfiguration.propertyAttribute}("${f.name.value}")]`,
620631
`public ${inputTypeName} ${propertyName} { get; set; }`,
621632
].join('\n') + '\n',
622633
);
@@ -642,13 +653,13 @@ ${this._getOperationMethod(node)}
642653
node.values
643654
?.map(
644655
v =>
645-
`[EnumMember(Value = "${v.name.value}")]\n${this._parsedConfig.memberNamingFunction(v.name.value)}`,
656+
`${this.config.jsonAttributesConfiguration.enumConfiguration.enumMemberAttribute(v.name.value)}\n${this._parsedConfig.memberNamingFunction(v.name.value)}`,
646657
)
647658
.join(',\n'),
648659
),
649660
);
650661

651-
const enumWithAttributes = `[DataContract]\n[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]\n${enumDefinition.string}`;
662+
const enumWithAttributes = `${this.config.jsonAttributesConfiguration.enumConfiguration.decorator}\n${enumDefinition.string}`;
652663

653664
return indentMultiline(enumWithAttributes, 2);
654665
}

packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { getJsonAttributeSourceConfiguration } from '@graphql-codegen/c-sharp-common';
12
import { Types } from '@graphql-codegen/plugin-helpers';
23
import '@graphql-codegen/testing';
34
import { buildSchema, parse } from 'graphql';
5+
import each from 'jest-each';
46
import { CSharpOperationsRawPluginConfig } from '../src/config.js';
57
import { plugin } from '../src/index.js';
68

@@ -45,6 +47,26 @@ describe('C# Operations', () => {
4547
})) as Types.ComplexPluginOutput;
4648
expect(result.content).toContain('namespace MyCompany.MyGeneratedGql {');
4749
});
50+
51+
each(['Newtonsoft.Json', 'System.Text.Json']).it(
52+
`Should include configured '%s' using directives`,
53+
async source => {
54+
const schema = buildSchema(/* GraphQL */ `
55+
enum ns {
56+
dummy
57+
}
58+
`);
59+
const config: CSharpOperationsRawPluginConfig = {
60+
jsonAttributesSource: source,
61+
};
62+
const result = (await plugin(schema, [], config, {
63+
outputFile: '',
64+
})) as Types.ComplexPluginOutput;
65+
const jsonConfig = getJsonAttributeSourceConfiguration(source);
66+
67+
expect(result.content).toContain(`using ${jsonConfig.namespace};`);
68+
},
69+
);
4870
});
4971

5072
describe('Query', () => {
@@ -806,30 +828,36 @@ describe('C# Operations', () => {
806828
public static GraphQLRequest getOnNotifyThemGQL() {
807829
`);
808830
});
809-
it('Should generate scalar response class', async () => {
810-
const schema = buildSchema(/* GraphQL */ `
811-
type Subscription {
812-
you: Int!
813-
}
814-
`);
815-
const operation = parse(/* GraphQL */ `
816-
subscription onNotifyYou {
817-
you
818-
}
819-
`);
820-
const result = (await plugin(
821-
schema,
822-
[{ location: '', document: operation }],
823-
{ typesafeOperation: true },
824-
{ outputFile: '' },
825-
)) as Types.ComplexPluginOutput;
826-
expect(result.content).toBeSimilarStringTo(`
831+
832+
each(['Newtonsoft.Json', 'System.Text.Json']).it(
833+
`Should generate scalar response class using '%s' source`,
834+
async source => {
835+
const schema = buildSchema(/* GraphQL */ `
836+
type Subscription {
837+
you: Int!
838+
}
839+
`);
840+
const operation = parse(/* GraphQL */ `
841+
subscription onNotifyYou {
842+
you
843+
}
844+
`);
845+
const jsonConfig = getJsonAttributeSourceConfiguration(source);
846+
847+
const result = (await plugin(
848+
schema,
849+
[{ location: '', document: operation }],
850+
{ typesafeOperation: true, jsonAttributesSource: source },
851+
{ outputFile: '' },
852+
)) as Types.ComplexPluginOutput;
853+
expect(result.content).toBeSimilarStringTo(`
827854
public class Response {
828-
[JsonProperty("you")]
855+
[${jsonConfig.propertyAttribute}("you")]
829856
public int you { get; set; }
830857
}
831858
`);
832-
});
859+
},
860+
);
833861
it('Should generate nested response class', async () => {
834862
const schema = buildSchema(/* GraphQL */ `
835863
type Subscription {
@@ -1266,48 +1294,55 @@ describe('C# Operations', () => {
12661294
});
12671295

12681296
describe('MemberNamingConfig', () => {
1269-
it('Should generate enums with pascal case values', async () => {
1270-
const schema = buildSchema(/* GraphQL */ `
1271-
type Query {
1272-
myQuery: MyEnum!
1273-
}
1274-
enum MyEnum {
1275-
Value1
1276-
value2
1277-
anotherValue
1278-
LastValue
1279-
SHOUTY_SNAKE_VALUE
1280-
}
1281-
`);
1282-
const operation = parse(/* GraphQL */ `
1283-
query GetMyQuery {
1284-
myQuery
1285-
}
1286-
`);
1287-
1288-
const result = (await plugin(
1289-
schema,
1290-
[{ location: '', document: operation }],
1291-
{ typesafeOperation: true, memberNameConvention: 'pascalCase' },
1292-
{ outputFile: '' },
1293-
)) as Types.ComplexPluginOutput;
1294-
expect(result.content).toBeSimilarStringTo(`
1295-
[DataContract]
1296-
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
1297+
each(['System.Text.Json', 'Newtonsoft.Json']).it(
1298+
'Should generate enums with pascal case values for %s',
1299+
async source => {
1300+
const schema = buildSchema(/* GraphQL */ `
1301+
type Query {
1302+
myQuery: MyEnum!
1303+
}
1304+
enum MyEnum {
1305+
Value1
1306+
value2
1307+
anotherValue
1308+
LastValue
1309+
SHOUTY_SNAKE_VALUE
1310+
}
1311+
`);
1312+
const operation = parse(/* GraphQL */ `
1313+
query GetMyQuery {
1314+
myQuery
1315+
}
1316+
`);
1317+
const jsonConfig = getJsonAttributeSourceConfiguration(source);
1318+
1319+
const result = (await plugin(
1320+
schema,
1321+
[{ location: '', document: operation }],
1322+
{
1323+
typesafeOperation: true,
1324+
memberNameConvention: 'pascalCase',
1325+
jsonAttributesSource: source,
1326+
},
1327+
{ outputFile: '' },
1328+
)) as Types.ComplexPluginOutput;
1329+
expect(result.content).toBeSimilarStringTo(`
1330+
${jsonConfig.enumConfiguration.decorator}
12971331
public enum MyEnum {
1298-
[EnumMember(Value = "Value1")]
1332+
${jsonConfig.enumConfiguration.enumMemberAttribute('Value1')}
12991333
Value1,
1300-
[EnumMember(Value = "value2")]
1334+
${jsonConfig.enumConfiguration.enumMemberAttribute('value2')}
13011335
Value2,
1302-
[EnumMember(Value = "anotherValue")]
1336+
${jsonConfig.enumConfiguration.enumMemberAttribute('anotherValue')}
13031337
AnotherValue,
1304-
[EnumMember(Value = "LastValue")]
1338+
${jsonConfig.enumConfiguration.enumMemberAttribute('LastValue')}
13051339
LastValue,
1306-
[EnumMember(Value = "SHOUTY_SNAKE_VALUE")]
1340+
${jsonConfig.enumConfiguration.enumMemberAttribute('SHOUTY_SNAKE_VALUE')}
13071341
ShoutySnakeValue
13081342
}
13091343
`);
1310-
});
1344+
},
1345+
);
13111346

13121347
it('Should generate input classes with pascal case property names', async () => {
13131348
const schema = buildSchema(/* GraphQL */ `

packages/plugins/c-sharp/c-sharp/src/config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { MemberNameConventionConfig } from '@graphql-codegen/c-sharp-common';
1+
import { JsonAttributesSource, MemberNameConventionConfig } from '@graphql-codegen/c-sharp-common';
22
import { EnumValuesMap, RawConfig } from '@graphql-codegen/visitor-plugin-common';
3-
import { JsonAttributesSource } from './json-attributes.js';
43

54
/**
65
* @description This plugin generates C# `class` identifier for your schema types.

0 commit comments

Comments
 (0)