From a481bc156279030c95bd3e09d2a29aea66af0725 Mon Sep 17 00:00:00 2001 From: matthewpeterkort Date: Mon, 6 Jan 2025 16:27:16 -0800 Subject: [PATCH 1/2] bugfix schema translator --- README.md | 8 ++++++ graphql/convertSchema.go | 53 ++++++++++++++++++++++++++-------------- graphql/griptographql.go | 5 +++- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 58df221..2034b3d 100644 --- a/README.md +++ b/README.md @@ -113,3 +113,11 @@ specify custom namespace for edge generation with "namespace" key in extra args, ``` jsonschemagraph gen-dir ../iceberg/schemas/graph DATA OUT --extraArgs '{"auth_resource_path": "/programs/ohsu/projects/test", "namespace": "CALIPERIDP.org"}' ``` + +### Example commands for generating graphql schema from jsonschema + +``` +jsonschemagraph gen-graphql --graphName CALIPER --jsonSchema graph-fhir.json --configPath config.yaml --writeIntermediateFile +``` + +This translator does not support codeable references diff --git a/graphql/convertSchema.go b/graphql/convertSchema.go index dd17f30..1a5e144 100644 --- a/graphql/convertSchema.go +++ b/graphql/convertSchema.go @@ -56,7 +56,7 @@ func LowerFirstLetter(s string) string { func generateQueryList(classes []string) { for i, v := range classes { - classes[i] = LowerFirstLetter(classes[i]) + "(offset: Int first: Int filter: JSON sort: JSON accessibility: Accessibility = all format: Format = json): [" + v + "Type]" + classes[i] = LowerFirstLetter(classes[i]) + "(offset: Int first: Int filter: JSON sort: JSON accessibility: Accessibility = all format: Format = json): [" + v + "Type!]!" } } @@ -108,8 +108,8 @@ func ParseIntoGraphqlSchema(relpath string, graphName string, vertexSubset []str } if ext, ok := class.Extensions[compile.GraphExtensionTag]; ok { - enumData := map[string][]string{} - enumSeen := map[string]bool{} + unionData := map[string][]string{} + unionSeen := map[string]bool{} for _, target := range ext.(compile.GraphExtension).Targets { parts := strings.Split(target.Rel, "_") RegexMatch := target.TargetHints.RegexMatch[0][:len(target.TargetHints.RegexMatch[0])-2] @@ -121,24 +121,39 @@ func ParseIntoGraphqlSchema(relpath string, graphName string, vertexSubset []str } vertexData[parts[0]] = RegexMatch continue + } else if len(parts) == 2 { + base, targetType := parts[0], parts[len(parts)-1] + if targetType != RegexMatch { + if value, ok := vertexData[parts[0]]; ok && value != nil { + strValue := "" + if slice, isSlice := value.([]any); isSlice && len(slice) > 0 { + strValue = slice[0].(string) + } else if s, isString := value.(string); isString { + strValue = s + } + if len(strValue) >= 5 && strings.HasSuffix(strValue, "Union") { + vertexData[parts[0]] = strValue[:len(strValue)-5] + } + } else { + fmt.Printf("Key not found or value is nil: %s\n %s", parts[0], vertexData) + } + continue + } + unionTitle := fmt.Sprintf("%s%s", class.Title, cases.Title(language.Und, cases.NoLower).String(base)) + "Union" + if _, seen := unionSeen[targetType+unionTitle]; !seen { + vertexData[base] = unionTitle + unionSeen[targetType+unionTitle] = true + unionData[unionTitle] = append(unionData[unionTitle], targetType) + } } - base, targetType := parts[0], parts[len(parts)-1] - // In places where there are in-node traversals before hitting an edge, need to - // continue with execution to avoid creating a redundant enum. - if targetType != RegexMatch { - continue - } - unionTitle := fmt.Sprintf("%s%s", class.Title, cases.Title(language.Und, cases.NoLower).String(base)) + "Union" - if _, seen := enumSeen[targetType+unionTitle]; !seen { - vertexData[base] = unionTitle - enumSeen[targetType+unionTitle] = true - enumData[unionTitle] = append(enumData[unionTitle], targetType) - } + /* else { base, targetType := parts[0], parts[len(parts)-1] + fmt.Println("BASE: ", base, "TARGET TYPE: ", targetType) */ + } - if enumData != nil { - for k, v := range enumData { - enum := map[string]any{"data": map[string]any{k: v}, "label": "Vertex", "gid": "Union"} - graphSchema["vertices"] = append(graphSchema["vertices"].([]map[string]any), enum) + if unionData != nil { + for k, v := range unionData { + union := map[string]any{"data": map[string]any{k: v}, "label": "Vertex", "gid": "Union"} + graphSchema["vertices"] = append(graphSchema["vertices"].([]map[string]any), union) } } } diff --git a/graphql/griptographql.go b/graphql/griptographql.go index a29225a..1089b1b 100644 --- a/graphql/griptographql.go +++ b/graphql/griptographql.go @@ -26,6 +26,8 @@ func GripGraphqltoGraphql(graph *gripql.Graph) string { schemaBuilder.WriteString("enum Format {\n json\n tsv\n csv\n}\n") for _, v := range graph.Vertices { + //fmt.Println("V: ", v) + //fmt.Printf("BREAK: \n\n") if v.Gid != "Query" { executedFirstBlock := false for name, values := range v.Data.AsMap() { @@ -51,11 +53,12 @@ func GripGraphqltoGraphql(graph *gripql.Graph) string { for field, fieldType := range v.Data.AsMap() { strFieldType, ok := fieldType.(string) if ok && (strings.HasSuffix(strFieldType, "Type") || strings.HasSuffix(strFieldType, "Union")) { - schemaBuilder.WriteString(fmt.Sprintf(" %s(offset: Int first: Int filter: JSON sort: JSON accessibility: Accessibility = all format: Format = json): [%s]\n", field, strFieldType)) + schemaBuilder.WriteString(fmt.Sprintf(" %s(offset: Int first: Int filter: JSON sort: JSON accessibility: Accessibility = all format: Format = json): %s!\n", field, strFieldType)) } else { schemaBuilder.WriteString(fmt.Sprintf(" %s: %s\n", field, fieldType)) } } + schemaBuilder.WriteString(" auth_resource_path: String\n") schemaBuilder.WriteString("}\n") } } else { From 8f8c5ba1ab3cfa7a900ed69428a034337c16839b Mon Sep 17 00:00:00 2001 From: matthewpeterkort Date: Wed, 8 Jan 2025 15:46:05 -0800 Subject: [PATCH 2/2] fix primitive type renderings --- graphql/convertSchema.go | 35 +++++++++++++++-------------------- graphql/parse.go | 14 ++++++-------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/graphql/convertSchema.go b/graphql/convertSchema.go index 1a5e144..6ac4dea 100644 --- a/graphql/convertSchema.go +++ b/graphql/convertSchema.go @@ -3,9 +3,9 @@ package graphql import ( "encoding/json" "fmt" - "log" "os" "path/filepath" + "reflect" "slices" "strings" "unicode" @@ -60,6 +60,10 @@ func generateQueryList(classes []string) { } } +func isSlice(v interface{}) bool { + return reflect.TypeOf(v).Kind() == reflect.Slice +} + func ParseIntoGraphqlSchema(relpath string, graphName string, vertexSubset []string, writeFile bool) ([]*gripql.Graph, error) { out, err := graph.Load(relpath) if err != nil { @@ -85,25 +89,16 @@ func ParseIntoGraphqlSchema(relpath string, graphName string, vertexSubset []str continue } - vertVal := ParseSchema(sch) - switch vertVal.(type) { - case string: - vertexData[key] = vertVal.(string) - case int: - vertexData[key] = vertVal.(int) - case bool: - vertexData[key] = vertVal.(bool) - case float64: - vertexData[key] = vertVal.(float64) - case []any: - if vertVal.([]any)[0].(string) == "Resource" { - vertVal.([]any)[0] = "ResourceUnion" - } - vertexData[key] = vertVal.([]any) - case nil: - default: - log.Printf("ERR State for type: ", vertVal) - continue + value := ParseSchema(sch) + + // Fields with edges that aren't defined in our internal schema are not present in the graphql schema either + if value == nil { + fmt.Printf("WARNING: key %s on type %s may not be supported\n", key, class.Title) + } else if isSlice(value) && value.([]any)[0] == "Resource" { + value.([]any)[0] = "ResourceUnion" + vertexData[key] = value + } else { + vertexData[key] = value } } diff --git a/graphql/parse.go b/graphql/parse.go index 1a29652..0bec896 100644 --- a/graphql/parse.go +++ b/graphql/parse.go @@ -3,23 +3,20 @@ package graphql import ( "fmt" "slices" - "strings" "github.com/bmeg/jsonschema/v5" ) func jsontographlprimitiveType(returnType any) any { - switch returnType.(type) { - case string: + switch returnType { + case "string": return "String" - case int: + case "integer": return "Int" - case bool: + case "boolean": return "Boolean" - case float64: + case "number": return "Float" - case []any: - return []any{[]any{strings.Title(returnType.([]any)[0].(string))}} default: fmt.Println("ERR State for jsontographlprimitiveType: ", returnType) return "" @@ -37,6 +34,7 @@ func ParseSchema(schema *jsonschema.Schema) any { if schema.Items2020 != nil { if schema.Items2020.Ref != nil && schema.Items2020.Ref.Title != "" { + // Don't include keys that contain references which types can't be discerned from reading the schema if slices.Contains([]string{"Reference", "FHIRPrimitiveExtension"}, schema.Items2020.Ref.Title) { return nil }