Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #46 fix #47

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ typings/

haxelib/gql2hx.zip
.tmp
.last_build.json
2 changes: 2 additions & 0 deletions .vscode/debug.hxml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

-resource test.gql@injected_gql

-lib hxnodejs

# Test
-debug
-cp .
Expand Down
15 changes: 15 additions & 0 deletions README-DEBUG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#### Debug

```
# vscode from project root
code -n .
```

The 'Launch via NPM' debug configuration will run the code in test/manual_debug/Test.hx

You can change the GQL you are attempting to generate code for by linking `test/manual_debug/test.gql` to the desired file. This GQL text is available as a Resource in Haxe land by the hidden build file `${projectRoot}.vscode/debug.hxml`

```
# snip from debug.html .. note test.gql maps to the symbol is injected_gql
-resource test.gql@injected_gql
```
7 changes: 7 additions & 0 deletions bit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
name: haxe-graphql
build_ver: BW2
builder: NullBuilder
dependencies:
bit:
- gql2hx
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
"scripts": {
"build": "cd test/manual_debug && haxe ../../.vscode/debug.hxml",
"debug": "cd test/manual_debug && npm run build && node --nolazy --inspect-brk=13729 test.js"
},
"dependencies": {
"commander": "^11.1.0"
}
}
8 changes: 8 additions & 0 deletions proj/gql2hx-npm/bit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
name: gql2hx
build_ver: BW2
builder: ShellCmdBuilder
type: in-house
steps:
- cmd: npm install
- cmd: npm run-script build
32 changes: 25 additions & 7 deletions proj/gql2hx-npm/package-lock.json

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

108 changes: 86 additions & 22 deletions proj/hxgen/src/graphql/HaxeGenerator.hx
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,15 @@ class HaxeGenerator
case TBasic(tname) | TScalar(tname) | TEnum(tname, _):
error('${ err_prefix }Expecting type ${ tname } to have field ${ name }!', true);
case TStruct(tname, fields):
var field = fields[name];
if (field==null) error('${ err_prefix }Expecting type ${ tname } to have field ${ name }!', true);
if (path.length==0) {
var field:GQLFieldType;
// Issue 43 - support __typename instrinsic
if(name == '__typename') {
field = { type:TPath('String'), is_array:false, is_optional:true };
} else {
field = fields[name];
}
if (field==null) error('${ err_prefix }Expecting type ${ tname } to have field ${ name }!', true);
if (path.length==0) {
resolve_type(field.type, err_prefix);
return field;
}
Expand Down Expand Up @@ -764,7 +770,6 @@ class HaxeGenerator
next_type_path.push(name);
var resolved = resolve_field(next_type_path, gt_info);
var type = resolve_type(resolved.type);

switch type {
case TBasic(tname) | TScalar(tname) | TEnum(tname, _):
if (has_sub_selections) {
Expand All @@ -776,14 +781,17 @@ class HaxeGenerator
error('Must specify sub-fields of ${ tname } at ${ type_path.join(".") } of operation ${ gt_info.debug_name }', true);
}
if (is_union(tname)) {
if (field_node.selectionSet.selections.find(function(sn) return sn.kind==Kind.FIELD)!=null) {
if (field_node.selectionSet.selections.find(function(sn) {
// Issue 43 - support __typename instrinsic
return sn.kind==Kind.FIELD && Reflect.field(cast sn, 'name').value!='__typename';
})!=null) {
error('Can only specify fragment selections of union ${ tname } at ${ type_path.join(".") } of operation ${ gt_info.debug_name }', true);
}
}

var sub_type_name = (depth==0 && StringTools.endsWith(type_name, OP_OUTER_SUFFIX)) ?
StringTools.replace(type_name, OP_OUTER_SUFFIX, OP_INNER_SUFFIX) : type_name+DEFAULT_SEPARATOR+name;

var sub_type = generate_type_based_on_selection_set(sub_type_name, gt_info, field_node.selectionSet, tname, depth+1);
var f = { type:null, is_array:resolved.is_array, is_optional:resolved.is_optional };
fields[alias] = f;
Expand All @@ -809,7 +817,7 @@ class HaxeGenerator
} else {
return _types_by_name[type_name];
}

}

// Per the discussion at https://github.com/facebook/graphql/issues/204, the operation
Expand Down Expand Up @@ -876,7 +884,8 @@ abstract ID(String) to String from String {\n // Relaxed safety -- allow implic
for (name in _types_by_name.keys()) {
if (_basic_types.indexOf(name)>=0) continue;
var type = _types_by_name[name];
stdout_writer.append( GQLTypeTools.type_to_string(type) );
// Issue #43 - inject type map so we can use __typename for unions
stdout_writer.append( GQLTypeTools.type_to_string(type, _types_by_name) );
stdout_writer.append('');
}

Expand Down Expand Up @@ -967,12 +976,11 @@ class GQLTypeTools
var field = { name:field_name, kind:FVar(ct, null), meta:[], pos:FAKE_POS };
if (gql_f.is_optional) field.meta.push({ name:":optional", pos:FAKE_POS });
return field;

case TAnon(any): throw 'Non-struct types are not supported in TAnon: ${ any }';
}
}

public static function type_to_string(td:GQLTypeDefinition):String {
public static function type_to_string(td:GQLTypeDefinition, types_by_name:StringMapAA<GQLTypeDefinition>):String {
var p = new CustomPrinter(" ", true);
switch td {
case TBasic(tname): return '';
Expand All @@ -991,23 +999,79 @@ class GQLTypeTools
return p.printTypeDefinition( haxe_td );
case TUnion(tname, type_paths):
var writer = new StringWriter();
var union_types_note = type_paths.map(function(t) return t).join(" | ");
writer.append('/* union '+tname+' = ${ union_types_note } */');
writer.append('abstract '+tname+'(Dynamic) {');
for (type_name in type_paths) {
writer.append(' @:from static inline function from${ type_name }(v:${ type_name }):${ tname } return cast v;');
var as_name = type_name;
var sep:String = HaxeGenerator.UNION_SELECTION_SEPARATOR;
if (as_name.indexOf(sep)>=0) {
as_name = as_name.substr(as_name.indexOf(sep)+sep.length);
}
writer.append(' public inline function as${ HaxeGenerator.DEFAULT_SEPARATOR }${ as_name }():${ type_name } return cast this;');
if(union_has_typename(types_by_name, type_paths)) {
generate_union_with_typename(tname, type_paths, types_by_name, writer);
} else {
generate_union(tname, type_paths, types_by_name, writer);
}
writer.append('}');
return writer.toString();
}
}

private static function generate_union_with_typename(tname:String, type_paths:Array<String>, types_by_name:StringMapAA<GQLTypeDefinition>, writer:StringWriter) {

// Generate the enum definition
var enum_val_names= [];
var enum_vals = type_paths.map(function(type_path) {
var r = ~/(ON_.*$)/;
r.match(type_path);
var name = r.matched(1);
enum_val_names.push(name);
return {name:'${name}(v:${type_path});'};
});
var union_enum_template_str =
'enum ${ tname }Enum {\n::foreach enum_vals:: ::name::\n::end::}';
var enum_template = new haxe.Template(union_enum_template_str);
var union_enum_str = enum_template.execute({enum_vals:enum_vals});
writer.append(union_enum_str);
var union_types_note = type_paths.map(function(t) return t).join(" | ");
writer.append('/* union '+tname+' = ${ union_types_note } */');
writer.append('abstract '+tname+'(Dynamic) {');

// Generate the tname abstract with the as_enum() method
var paths = [];
for(i in 0...type_paths.length) {
paths.push({index:i, path:type_paths[i], enum_val_name:enum_val_names[i]});
}
var as_enum_template_str = ' public function as_enum():::tname::Enum {
var re = new EReg(this.__typename, "");
::foreach paths::if(re.match("::path::")) { return ::enum_val_name::(this);}
::end::throw "invalid type";
}';
var as_enum_template = new haxe.Template(as_enum_template_str);
var as_enum_str = as_enum_template.execute({paths:paths, tname:tname});
writer.append(as_enum_str);
writer.append('}');
}

private static function generate_union(tname:String, type_paths:Array<String>, types_by_name:StringMapAA<GQLTypeDefinition>, writer:StringWriter) {
var union_types_note = type_paths.map(function(t) return t).join(" | ");
writer.append('/* union '+tname+' = ${ union_types_note } */');
writer.append('abstract '+tname+'(Dynamic) {');
for (type_name in type_paths) {
writer.append(' @:from static inline function from${ type_name }(v:${ type_name }):${ tname } return cast v;');
var as_name = type_name;
var sep:String = HaxeGenerator.UNION_SELECTION_SEPARATOR;
if (as_name.indexOf(sep)>=0) {
as_name = as_name.substr(as_name.indexOf(sep)+sep.length);
}
writer.append(' public inline function as${ HaxeGenerator.DEFAULT_SEPARATOR }${ as_name }():${ type_name } return cast this;');
}
writer.append('}');
return writer.toString();
}

private static function union_has_typename(types_by_name:StringMapAA<GQLTypeDefinition>, type_paths:Array<String>) {
// __typename on the union is reflected in all the type_paths so we only have to check the first one
var union_type = types_by_name[type_paths[0]];
switch union_type {
case TStruct(name, fields):
return fields.exists('__typename');
default:
return false;
}
}

private static var FAKE_POS = { min:0, max:0, file:'Untitled' };

}
Expand Down
4 changes: 1 addition & 3 deletions proj/webdemo/gql2hx_demo.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/buddy/Main.hx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Main {
new tests.issues.Issue30(),
new tests.issues.Issue31(),
new tests.issues.Issue35(),
new tests.issues.Issue43(),
], reporter);

runner.run();
Expand Down
64 changes: 64 additions & 0 deletions test/buddy/tests/issues/Issue43.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package tests.issues;

import buddy.*;
using buddy.Should;

class Issue43 extends BuddySuite
{
public static inline var gql =
'
enum ContentModuleType
{
POLL
TASKLIST
}
type TaskListModule {
content_type: ContentModuleType!
tasklist_id: ID!
}
type PollModule {
content_type: ContentModuleType!
poll_id: ID!
}
union ContentModule = PollModule | TaskListModule
type Query
{
content_module_by_id(id: ID!): ContentModule!
}

query GetContentModuleById($$id: ID!) {
content_module_by_id(id: $$id) {
__typename
... on TaskListModule {
content_type
tasklist_id
}
... on PollModule {
content_type
poll_id
}
}
}

';

public function new() {
describe("Issue43:", {

var parser:graphql.parser.Parser;

it('should parse this query document without error', {
parser = new graphql.parser.Parser(gql);
});

var haxe_code:String;
it("The HaxeGenerator should generate Haxe...", {
var result = graphql.HaxeGenerator.parse(parser.document);
haxe_code = result.stdout;
});

});

}

}
2 changes: 1 addition & 1 deletion test/manual_debug/MutationTest.hx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package;
1package;

class Test
{
Expand Down
16 changes: 16 additions & 0 deletions test/manual_debug/Test.hx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package;
import sys.io.File;
//import Playground;

class Test
{
Expand All @@ -13,6 +15,19 @@ class Test
// test('schema-kitchen-sink.graphql');
// var source = sys.io.File.getContent(fn);

// Idea is to return the SDL Union as a Haxe Enum
// var pm = ContentModule.OnPollModule({content_type:POLL, poll_id:'1234'});
// var tm = ContentModule.OnTasklistModule({content_type:TASKLIST,tasklist_id: '1234'});
// var cm = SagaAPICMSClient.GetContentModuleById(/*..*/);
// switch (cm) {
// case ContentModule.OnPollModule({content_type:POLL, poll_id:'1234'}):
// trace('poll');
// case ContentModule.OnTasklistModule({content_type:TASKLIST,tasklist_id: '1234'}):
// trace('tasklist');
// default:
// trace('default');
// }

trace('============================================================');
trace('============================================================');
trace('============================================================');
Expand All @@ -33,6 +48,7 @@ class Test
if (result.stderr.length>0) {
trace('Error:\n${ result.stderr }');
} else {
File.saveContent("GeneratedHaxe.hx", cast result.stdout);
trace(result.stdout);
}
trace('============================================================');
Expand Down
Loading