AEB-labs / graphql-weaver

A tool to combine, link and transform GraphQL schemas
MIT License
240 stars 20 forks source link

How to rename fields properly? #55

Open FX-HAO opened 5 years ago

FX-HAO commented 5 years ago

I want to camelize field and argument names, I thought it was simple, just renaming the snake-style fields to camel-style. So I wrote a custom module as the following:

export class SnakeToCamelFieldModule implements PipelineModule {
    private snakeToCamelFieldSchema = new SnakeToCamelFieldSchema();

    transformSchema(schema: GraphQLSchema): GraphQLSchema {
        return transformSchema(schema, this.snakeToCamelFieldSchema);
    }
}

function isSpecificCase(s: string, converter: (value: string, locale?: string) => string ) {
    return s === converter(s);
}

class SnakeToCamelFieldSchema implements SchemaTransformer {
    private convertMap: { [key: string]: string; } = {};
    transformField(field: GraphQLNamedFieldConfig<any, any>, context: any) {
        // Rename a field in a type
        if (isSpecificCase(field.name, snakeCase)) {
            const camelName = camelCase(field.name);
            this.convertMap[camelName] = field.name;
            return {
                ...field,
                name: camelName
            };
        }
        return field;
    }
    renameField(name: string): string {
        return this.convertMap[name] || name;
    }
}

The schema was exactly what I want, but I also need to rename the query back. Then I updated FieldNode's name, but it failed and I got Failed to determine type of SelectionSet node. It seems like some validations called before executing the query. Then I thought maybe graphql aliases could help, so I updated the code as the following:

export class SnakeToCamelFieldModule implements PipelineModule {
    private snakeToCamelFieldSchema = new SnakeToCamelFieldSchema();

    transformSchema(schema: GraphQLSchema): GraphQLSchema {
        return transformSchema(schema, this.snakeToCamelFieldSchema);
    }

    transformQuery(query: Query): Query {
        const definitions: Array<OperationDefinitionNode> = query.document.definitions.filter(
            (e: DefinitionNode): e is OperationDefinitionNode => e.kind === "OperationDefinition");

        const fieldNodes = flatten(definitions.map(def => collectFieldNodes(def)));
        fieldNodes.map((field: any) => {
            const originalName = this.snakeToCamelFieldSchema.renameField(field.name.value);
            if (originalName !== field.name.value) {
                field.alias = {
                    kind: "Name",
                    value: field.name.value
                };
                field.name.value = originalName;
            }
        });

        return query;
    }
}

It only partly succeeded, no errors occured this time, some queries like { getUsername } succeeded, it returned { "data": { "getUsername": "haofuxin" } }. But some queries like

{
  relay {
    routes {
      edges {
        node {
          isActive
          id
        }
      }
    }
}

returned

{
  "data": {
    "relay": {
      "routes": {
        "edges": [
          {
            "node": {
              "id": "Um91dGUtYXNjZW5kXzE2NA=="
            }
          }
        ]
      }
    }
  }
}

isActive was missing. After diving in for a while, I found that isActive was filtered out in https://github.com/graphql/graphql-js/blob/ed74228dc8540e3d2681cb6da473e7ce5edb7850/src/execution/execute.js#L649-L655 . fieldName is is_active rather than isActive.