Skip to content

Commit b64475b

Browse files
authored
Implement additional configuration for C# plugin related to JSON attributes, remove redundant dependency on GraphQL namespace (#6342)
1 parent 125c995 commit b64475b

File tree

5 files changed

+239
-21
lines changed

5 files changed

+239
-21
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-codegen/c-sharp': minor
3+
---
4+
5+
Add options for JSON attributes configuration

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { RawConfig, EnumValuesMap } from '@graphql-codegen/visitor-plugin-common';
2+
import { JsonAttributesSource } from './json-attributes';
23

34
/**
45
* @description This plugin generates C# `class` identifier for your schema types.
@@ -77,4 +78,36 @@ export interface CSharpResolversPluginRawConfig extends RawConfig {
7778
* ```
7879
*/
7980
emitRecords?: boolean;
81+
82+
/**
83+
* @default true
84+
* @description Should JSON attributes be emitted for produced types and properties ot not
85+
*
86+
* @exampleMarkdown
87+
* ```yml
88+
* generates:
89+
* src/main/c-sharp/my-org/my-app/Types.cs:
90+
* plugins:
91+
* - c-sharp
92+
* config:
93+
* emitJsonAttributes: false
94+
* ```
95+
*/
96+
emitJsonAttributes?: boolean;
97+
98+
/**
99+
* @default Newtonsoft.Json
100+
* @description Library that should be used to emit JSON attributes. Ignored when `emitJsonAttributes` is `false` or not specified
101+
*
102+
* @exampleMarkdown
103+
* ```yml
104+
* generates:
105+
* src/main/c-sharp/my-org/my-app/Types.cs:
106+
* plugins:
107+
* - c-sharp
108+
* config:
109+
* jsonAttributesSource: System.Text.Json
110+
* ```
111+
*/
112+
jsonAttributesSource?: JsonAttributesSource;
80113
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export type JsonAttributesSource = 'Newtonsoft.Json' | 'System.Text.Json';
2+
3+
function unsupportedSource(attributesSource: JsonAttributesSource): void {
4+
throw new Error(`Unsupported JSON attributes source: ${attributesSource}`);
5+
}
6+
7+
export class JsonAttributesSourceConfiguration {
8+
readonly namespace: string;
9+
readonly propertyAttribute: string;
10+
readonly requiredAttribute: string;
11+
12+
constructor(namespace: string, propertyAttribute: string, requiredAttribute: string) {
13+
this.namespace = namespace;
14+
this.propertyAttribute = propertyAttribute;
15+
this.requiredAttribute = requiredAttribute;
16+
}
17+
}
18+
19+
const newtonsoftConfiguration = new JsonAttributesSourceConfiguration(
20+
'Newtonsoft.Json',
21+
'JsonProperty',
22+
'JsonRequired'
23+
);
24+
25+
// System.Text.Json does not have support of `JsonRequired` alternative (as for .NET 5)
26+
const systemTextJsonConfiguration = new JsonAttributesSourceConfiguration('System.Text.Json', 'JsonPropertyName', null);
27+
28+
export function getJsonAttributeSourceConfiguration(attributesSource: JsonAttributesSource) {
29+
switch (attributesSource) {
30+
case 'Newtonsoft.Json':
31+
return newtonsoftConfiguration;
32+
case 'System.Text.Json':
33+
return systemTextJsonConfiguration;
34+
}
35+
unsupportedSource(attributesSource);
36+
}

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

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,25 @@ import {
3939
getListTypeField,
4040
} from '../../common/common';
4141
import { pascalCase } from 'change-case-all';
42+
import {
43+
JsonAttributesSource,
44+
JsonAttributesSourceConfiguration,
45+
getJsonAttributeSourceConfiguration,
46+
} from './json-attributes';
4247

4348
export interface CSharpResolverParsedConfig extends ParsedConfig {
4449
namespaceName: string;
4550
className: string;
4651
listType: string;
4752
enumValues: EnumValuesMap;
4853
emitRecords: boolean;
54+
emitJsonAttributes: boolean;
55+
jsonAttributesSource: JsonAttributesSource;
4956
}
5057

5158
export class CSharpResolversVisitor extends BaseVisitor<CSharpResolversPluginRawConfig, CSharpResolverParsedConfig> {
5259
private readonly keywords = new Set(csharpKeywords);
60+
private readonly jsonAttributesConfiguration: JsonAttributesSourceConfiguration;
5361

5462
constructor(rawConfig: CSharpResolversPluginRawConfig, private _schema: GraphQLSchema) {
5563
super(rawConfig, {
@@ -58,8 +66,14 @@ export class CSharpResolversVisitor extends BaseVisitor<CSharpResolversPluginRaw
5866
namespaceName: rawConfig.namespaceName || 'GraphQLCodeGen',
5967
className: rawConfig.className || 'Types',
6068
emitRecords: rawConfig.emitRecords || false,
69+
emitJsonAttributes: rawConfig.emitJsonAttributes ?? true,
70+
jsonAttributesSource: rawConfig.jsonAttributesSource || 'Newtonsoft.Json',
6171
scalars: buildScalarsFromConfig(_schema, rawConfig, C_SHARP_SCALARS),
6272
});
73+
74+
if (this._parsedConfig.emitJsonAttributes) {
75+
this.jsonAttributesConfiguration = getJsonAttributeSourceConfiguration(this._parsedConfig.jsonAttributesSource);
76+
}
6377
}
6478

6579
/**
@@ -78,7 +92,11 @@ export class CSharpResolversVisitor extends BaseVisitor<CSharpResolversPluginRaw
7892
}
7993

8094
public getImports(): string {
81-
const allImports = ['System', 'System.Collections.Generic', 'Newtonsoft.Json', 'GraphQL'];
95+
const allImports = ['System', 'System.Collections.Generic', 'System.ComponentModel.DataAnnotations'];
96+
if (this._parsedConfig.emitJsonAttributes) {
97+
const jsonAttributesNamespace = this.jsonAttributesConfiguration.namespace;
98+
allImports.push(jsonAttributesNamespace);
99+
}
82100
return allImports.map(i => `using ${i};`).join('\n') + '\n';
83101
}
84102

@@ -143,12 +161,25 @@ export class CSharpResolversVisitor extends BaseVisitor<CSharpResolversPluginRaw
143161
attributes.push(`[Obsolete("${deprecationReason}")]`);
144162
}
145163

146-
if (node.kind === Kind.FIELD_DEFINITION) {
147-
attributes.push(`[JsonProperty("${node.name.value}")]`);
164+
if (this._parsedConfig.emitJsonAttributes) {
165+
if (node.kind === Kind.FIELD_DEFINITION) {
166+
const jsonPropertyAttribute = this.jsonAttributesConfiguration.propertyAttribute;
167+
if (jsonPropertyAttribute != null) {
168+
attributes.push(`[${jsonPropertyAttribute}("${node.name.value}")]`);
169+
}
170+
}
148171
}
149172

150173
if (node.kind === Kind.INPUT_VALUE_DEFINITION && fieldType.isOuterTypeRequired) {
151-
attributes.push(`[JsonRequired]`);
174+
// Should be always inserted for required fields to use in `GetInputObject()` when JSON attributes are not used
175+
// or there are no JSON attributes in selected attribute source that provides `JsonRequired` alternative
176+
attributes.push('[Required]');
177+
if (this._parsedConfig.emitJsonAttributes) {
178+
const jsonRequiredAttribute = this.jsonAttributesConfiguration.requiredAttribute;
179+
if (jsonRequiredAttribute != null) {
180+
attributes.push(`[${jsonRequiredAttribute}]`);
181+
}
182+
}
152183
}
153184

154185
if (commentText || attributes.length > 0) {
@@ -377,8 +408,15 @@ ${classMembers}
377408
{
378409
var value = propertyInfo.GetValue(this);
379410
var defaultValue = propertyInfo.PropertyType.IsValueType ? Activator.CreateInstance(propertyInfo.PropertyType) : null;
380-
381-
var requiredProp = propertyInfo.GetCustomAttributes(typeof(JsonRequiredAttribute), false).Length > 0;
411+
${
412+
this._parsedConfig.emitJsonAttributes && this.jsonAttributesConfiguration.requiredAttribute != null
413+
? `
414+
var requiredProp = propertyInfo.GetCustomAttributes(typeof(${this.jsonAttributesConfiguration.requiredAttribute}Attribute), false).Length > 0;
415+
`
416+
: `
417+
var requiredProp = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false).Length > 0;
418+
`
419+
}
382420
if (requiredProp || value != defaultValue)
383421
{
384422
d[propertyInfo.Name] = value;

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

Lines changed: 121 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import '@graphql-codegen/testing';
22
import { buildSchema } from 'graphql';
33
import { plugin } from '../src/index';
44
import { CSharpResolversPluginRawConfig } from '../src/config';
5+
import { getJsonAttributeSourceConfiguration } from '../src/json-attributes';
6+
import each from 'jest-each';
57

68
describe('C#', () => {
79
describe('Using directives', () => {
@@ -15,9 +17,36 @@ describe('C#', () => {
1517

1618
expect(result).toContain('using System;');
1719
expect(result).toContain('using System.Collections.Generic;');
20+
});
21+
22+
it('Should include default JSON using directives', async () => {
23+
const schema = buildSchema(/* GraphQL */ `
24+
enum ns {
25+
dummy
26+
}
27+
`);
28+
const result = await plugin(schema, [], {}, { outputFile: '' });
29+
1830
expect(result).toContain('using Newtonsoft.Json;');
19-
expect(result).toContain('using GraphQL;');
2031
});
32+
33+
each(['Newtonsoft.Json', 'System.Text.Json']).it(
34+
`Should include configured '%s' using directives`,
35+
async source => {
36+
const schema = buildSchema(/* GraphQL */ `
37+
enum ns {
38+
dummy
39+
}
40+
`);
41+
const config: CSharpResolversPluginRawConfig = {
42+
jsonAttributesSource: source,
43+
};
44+
const result = await plugin(schema, [], config, { outputFile: '' });
45+
const jsonConfig = getJsonAttributeSourceConfiguration(source);
46+
47+
expect(result).toContain(`using ${jsonConfig.namespace};`);
48+
}
49+
);
2150
});
2251

2352
describe('Namespaces', () => {
@@ -232,6 +261,7 @@ describe('C#', () => {
232261
/// <summary>
233262
/// User id
234263
/// </summary>
264+
[Required]
235265
[JsonRequired]
236266
public int id { get; set; }
237267
`);
@@ -293,20 +323,46 @@ describe('C#', () => {
293323
expect(result).toContain('public class @public {');
294324
});
295325

296-
it('Should generate properties for types', async () => {
326+
each(['Newtonsoft.Json', 'System.Text.Json']).it(
327+
`Should generate properties for types using '%s' source`,
328+
async source => {
329+
const schema = buildSchema(/* GraphQL */ `
330+
type User {
331+
id: Int
332+
email: String
333+
}
334+
`);
335+
const config: CSharpResolversPluginRawConfig = {
336+
jsonAttributesSource: source,
337+
};
338+
const result = await plugin(schema, [], config, { outputFile: '' });
339+
const jsonConfig = getJsonAttributeSourceConfiguration(source);
340+
341+
expect(result).toBeSimilarStringTo(`
342+
[${jsonConfig.propertyAttribute}("id")]
343+
public int? id { get; set; }
344+
[${jsonConfig.propertyAttribute}("email")]
345+
public string email { get; set; }
346+
`);
347+
}
348+
);
349+
350+
it(`Should generate properties for types without JSON attributes`, async () => {
297351
const schema = buildSchema(/* GraphQL */ `
298352
type User {
299353
id: Int
300354
email: String
301355
}
302356
`);
303-
const result = await plugin(schema, [], {}, { outputFile: '' });
357+
const config: CSharpResolversPluginRawConfig = {
358+
emitJsonAttributes: false,
359+
};
360+
const result = await plugin(schema, [], config, { outputFile: '' });
361+
304362
expect(result).toBeSimilarStringTo(`
305-
[JsonProperty("id")]
306-
public int? id { get; set; }
307-
[JsonProperty("email")]
308-
public string email { get; set; }
309-
`);
363+
public int? id { get; set; }
364+
public string email { get; set; }
365+
`);
310366
});
311367

312368
it('Should generate summary header for class and properties', async () => {
@@ -406,7 +462,50 @@ describe('C#', () => {
406462

407463
describe('GraphQL Value Types', () => {
408464
describe('Scalar', () => {
409-
it('Should generate properties for mandatory scalar types', async () => {
465+
each(['Newtonsoft.Json', 'System.Text.Json']).it(
466+
`Should generate properties for mandatory scalar types using '%s' source`,
467+
async source => {
468+
const schema = buildSchema(/* GraphQL */ `
469+
input BasicTypeInput {
470+
intReq: Int!
471+
fltReq: Float!
472+
idReq: ID!
473+
strReq: String!
474+
boolReq: Boolean!
475+
}
476+
`);
477+
478+
const config: CSharpResolversPluginRawConfig = {
479+
jsonAttributesSource: source,
480+
};
481+
const result = await plugin(schema, [], config, { outputFile: '' });
482+
const jsonConfig = getJsonAttributeSourceConfiguration(source);
483+
const attributes =
484+
`
485+
[Required]
486+
` +
487+
(jsonConfig.requiredAttribute == null
488+
? ``
489+
: `
490+
[${jsonConfig.requiredAttribute}]
491+
`);
492+
493+
expect(result).toBeSimilarStringTo(`
494+
${attributes}
495+
public int intReq { get; set; }
496+
${attributes}
497+
public double fltReq { get; set; }
498+
${attributes}
499+
public string idReq { get; set; }
500+
${attributes}
501+
public string strReq { get; set; }
502+
${attributes}
503+
public bool boolReq { get; set; }
504+
`);
505+
}
506+
);
507+
508+
it(`Should generate properties for mandatory scalar types with JSON attributes disabled`, async () => {
410509
const schema = buildSchema(/* GraphQL */ `
411510
input BasicTypeInput {
412511
intReq: Int!
@@ -416,18 +515,21 @@ describe('C#', () => {
416515
boolReq: Boolean!
417516
}
418517
`);
419-
const result = await plugin(schema, [], {}, { outputFile: '' });
518+
const config: CSharpResolversPluginRawConfig = {
519+
emitJsonAttributes: false,
520+
};
521+
const result = await plugin(schema, [], config, { outputFile: '' });
420522

421523
expect(result).toBeSimilarStringTo(`
422-
[JsonRequired]
524+
[Required]
423525
public int intReq { get; set; }
424-
[JsonRequired]
526+
[Required]
425527
public double fltReq { get; set; }
426-
[JsonRequired]
528+
[Required]
427529
public string idReq { get; set; }
428-
[JsonRequired]
530+
[Required]
429531
public string strReq { get; set; }
430-
[JsonRequired]
532+
[Required]
431533
public bool boolReq { get; set; }
432534
`);
433535
});
@@ -514,8 +616,10 @@ describe('C#', () => {
514616
expect(result).toBeSimilarStringTo(`
515617
public IEnumerable<int> arr1 { get; set; }
516618
public IEnumerable<double?> arr2 { get; set; }
619+
[Required]
517620
[JsonRequired]
518621
public IEnumerable<int?> arr3 { get; set; }
622+
[Required]
519623
[JsonRequired]
520624
public IEnumerable<bool> arr4 { get; set; }
521625
`);
@@ -539,8 +643,10 @@ describe('C#', () => {
539643

540644
expect(result).toBeSimilarStringTo(`
541645
public IEnumerable<IEnumerable<int>> arr1 { get; set; }
646+
[Required]
542647
[JsonRequired]
543648
public IEnumerable<IEnumerable<IEnumerable<double?>>> arr2 { get; set; }
649+
[Required]
544650
[JsonRequired]
545651
public IEnumerable<IEnumerable<Complex>> arr3 { get; set; }
546652
`);

0 commit comments

Comments
 (0)