-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
546 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package typegen | ||
|
||
import ( | ||
"io" | ||
) | ||
|
||
type TypeGenerator struct { | ||
writer io.Writer | ||
GeneratePermissionTypes func() error | ||
GenerateRoleTypes func() error | ||
GenerateAuditLogTypes func() error | ||
} | ||
|
||
func NewTypeGenerator(w io.Writer) *TypeGenerator { | ||
return &TypeGenerator{writer: w} | ||
} | ||
|
||
// Permission types | ||
type PermissionResponse struct { | ||
Data []Permission `json:"data"` | ||
} | ||
|
||
type Permission struct { | ||
Slug string `json:"slug"` | ||
} | ||
|
||
// Role types | ||
type RoleResponse struct { | ||
Data []Role `json:"data"` | ||
} | ||
|
||
type Role struct { | ||
Slug string `json:"slug"` | ||
} | ||
|
||
// Audit Log types | ||
type AuditLogResponse struct { | ||
Data []AuditLogEvent `json:"data"` | ||
} | ||
|
||
type AuditLogEvent struct { | ||
Name string `json:"name"` | ||
Schema AuditLogSchema `json:"schema"` | ||
} | ||
|
||
type AuditLogSchema struct { | ||
ActorSchema AuditLogActorSchema `json:"actor"` | ||
TargetSchemas []AuditLogTargetSchema `json:"targets"` | ||
MetadataSchema *AuditLogMetadataSchema `json:"metadata"` | ||
} | ||
|
||
type AuditLogActorSchema struct { | ||
MetadataSchema *AuditLogMetadataSchema `json:"metadata"` | ||
} | ||
|
||
type AuditLogTargetSchema struct { | ||
Type string `json:"type"` | ||
MetadataSchema *AuditLogMetadataSchema `json:"metadata"` | ||
} | ||
|
||
type AuditLogMetadataSchema struct { | ||
Type string `json:"type"` | ||
Properties map[string]AuditLogMetadataProperty `json:"properties"` | ||
} | ||
|
||
type AuditLogMetadataProperty struct { | ||
Type string `json:"type"` | ||
Nullable *bool `json:"nullable,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
package typegen | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"golang.org/x/text/cases" | ||
"golang.org/x/text/language" | ||
) | ||
|
||
type PythonTypeGenerator struct { | ||
TypeGenerator | ||
} | ||
|
||
func NewPythonTypeGenerator(w io.Writer) *PythonTypeGenerator { | ||
return &PythonTypeGenerator{TypeGenerator: TypeGenerator{writer: w}} | ||
} | ||
|
||
// Permission Generator | ||
func (g *PythonTypeGenerator) GeneratePermissionsTypes() error { | ||
jsonData, err := os.ReadFile("permissions.json") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var response PermissionResponse | ||
if err := json.Unmarshal(jsonData, &response); err != nil { | ||
return fmt.Errorf("failed to parse permissions JSON: %w", err) | ||
} | ||
|
||
slugs := make([]string, len(response.Data)) | ||
for i, permission := range response.Data { | ||
slugs[i] = permission.Slug | ||
} | ||
|
||
return g.generateUnionType("WorkOSPermission", slugs) | ||
} | ||
|
||
// Role Generator | ||
func (g *PythonTypeGenerator) GenerateRolesTypes() error { | ||
jsonData, err := os.ReadFile("roles.json") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var response RoleResponse | ||
if err := json.Unmarshal(jsonData, &response); err != nil { | ||
return fmt.Errorf("failed to parse roles JSON: %w", err) | ||
} | ||
|
||
slugs := make([]string, len(response.Data)) | ||
for i, role := range response.Data { | ||
slugs[i] = role.Slug | ||
} | ||
|
||
return g.generateUnionType("WorkOSRole", slugs) | ||
} | ||
|
||
// Audit Log Generator | ||
func (g *PythonTypeGenerator) GenerateAuditLogsTypes() error { | ||
req, err := http.NewRequest("GET", "https://api.workos.com/audit_logs/actions", nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to create request: %w", err) | ||
} | ||
req.Header.Set("Authorization", "Bearer "+os.Getenv("WORKOS_API_KEY")) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("failed to fetch audit logs actions: %w", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
var response AuditLogResponse | ||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { | ||
return fmt.Errorf("failed to parse audit logs response: %w", err) | ||
} | ||
|
||
var interfaces []string | ||
var eventNames []string | ||
|
||
for _, action := range response.Data { | ||
interfaceName := g.generateInterfaceName(action.Name) | ||
eventNames = append(eventNames, interfaceName) | ||
|
||
interfaceContent := g.generateAuditLogEventInterface(action) | ||
interfaces = append(interfaces, interfaceContent) | ||
} | ||
|
||
// Add typing imports | ||
output := "from typing import Literal, NotRequired, Sequence, TypedDict, Union\n\n" | ||
|
||
output += strings.Join(interfaces, "\n") | ||
|
||
unionType := fmt.Sprintf("\nAuditLogEvent = Union[%s]\n", | ||
strings.Join(eventNames, ", ")) | ||
output += unionType | ||
|
||
_, err = fmt.Fprint(g.writer, output) | ||
return err | ||
} | ||
|
||
func (g *PythonTypeGenerator) generateTypedDictFromMap(name string, properties map[string]string) string { | ||
generatedCode := fmt.Sprintf("class %s(TypedDict):\n", name) | ||
propertiesBuffer := new(bytes.Buffer) | ||
|
||
for key, pythonType := range properties { | ||
fmt.Fprintf(propertiesBuffer, " %s: %s\n", key, pythonType) | ||
} | ||
|
||
generatedCode += propertiesBuffer.String() + "\n" | ||
return generatedCode | ||
} | ||
|
||
func (g *PythonTypeGenerator) generateTypedDictFromMetadataProperties(className string, metadataPropertyMap map[string]AuditLogMetadataProperty) string { | ||
return g.generateTypedDictFromMap(className, g.generateMetadataProperties(metadataPropertyMap)) | ||
} | ||
|
||
func (g *PythonTypeGenerator) generateAuditLogEventInterface(event AuditLogEvent) string { | ||
interfaceName := g.generateInterfaceName(event.Name) | ||
|
||
var generatedCode string | ||
|
||
// Map of top-level property keys and types | ||
properties := map[string]string{ | ||
"action": fmt.Sprintf("Literal['%s']", event.Name), | ||
"occurred_at": "str", | ||
"version": "NotRequired[int]", | ||
} | ||
|
||
actorProperties := map[string]string{ | ||
"id": "str", | ||
"name": "NotRequired[str]", | ||
} | ||
|
||
if event.Schema.ActorSchema.MetadataSchema != nil { | ||
actorMetadataInterfaceName := interfaceName + "ActorMetadata" | ||
generatedCode += g.generateTypedDictFromMetadataProperties(actorMetadataInterfaceName, event.Schema.ActorSchema.MetadataSchema.Properties) | ||
actorProperties["metadata"] = actorMetadataInterfaceName | ||
} | ||
|
||
// Add actor property | ||
actorInterfaceName := interfaceName + "Actor" | ||
generatedCode += g.generateTypedDictFromMap(actorInterfaceName, actorProperties) | ||
properties["actor"] = actorInterfaceName | ||
|
||
if len(event.Schema.TargetSchemas) > 0 { | ||
var targetTypes []string | ||
for _, target := range event.Schema.TargetSchemas { | ||
targetProps := map[string]string{ | ||
"type": fmt.Sprintf("Literal['%s']", target.Type), | ||
"id": "str", | ||
"name": "NotRequired[str]", | ||
} | ||
if target.MetadataSchema != nil { | ||
targetMetadataInterfaceName := interfaceName + "TargetMetadata" | ||
generatedCode += g.generateTypedDictFromMetadataProperties(targetMetadataInterfaceName, target.MetadataSchema.Properties) | ||
targetProps["metadata"] = targetMetadataInterfaceName | ||
} | ||
|
||
targetInterfaceName := fmt.Sprintf("%s%s%s", interfaceName, cases.Title(language.English).String(target.Type), "Target") | ||
generatedCode += g.generateTypedDictFromMap(targetInterfaceName, targetProps) | ||
targetTypes = append(targetTypes, targetInterfaceName) | ||
} | ||
|
||
if len(targetTypes) > 1 { | ||
properties["targets"] = fmt.Sprintf("Sequence[Union[%s]]", strings.Join(targetTypes, ", ")) | ||
} else { | ||
properties["targets"] = fmt.Sprintf("Sequence[%s]", targetTypes[0]) | ||
} | ||
} | ||
|
||
// Add context property | ||
contextInterfaceName := interfaceName + "Context" | ||
generatedCode += g.generateTypedDictFromMap(contextInterfaceName, map[string]string{ | ||
"location": "str", | ||
"user_agent": "NotRequired[str]", | ||
}) | ||
properties["context"] = contextInterfaceName | ||
|
||
// Add top-level metadata property | ||
if event.Schema.MetadataSchema != nil && len(event.Schema.MetadataSchema.Properties) > 0 { | ||
metadataInterfaceName := interfaceName + "Metadata" | ||
generatedCode += g.generateTypedDictFromMetadataProperties(metadataInterfaceName, event.Schema.MetadataSchema.Properties) | ||
properties["metadata"] = metadataInterfaceName | ||
} | ||
|
||
generatedCode += fmt.Sprintf("# Types for %ss\nclass %s(TypedDict):\n", interfaceName, interfaceName) | ||
propertiesBuffer := new(bytes.Buffer) | ||
|
||
for key, pythonType := range properties { | ||
fmt.Fprintf(propertiesBuffer, " %s: %s\n", key, pythonType) | ||
} | ||
|
||
generatedCode += propertiesBuffer.String() | ||
return generatedCode | ||
} | ||
|
||
func (g *PythonTypeGenerator) generateMetadataProperties(properties map[string]AuditLogMetadataProperty) map[string]string { | ||
convertedProperties := make(map[string]string) | ||
for key, prop := range properties { | ||
if prop.Nullable != nil && *prop.Nullable { | ||
convertedProperties[key] = fmt.Sprintf("NotRequired[%s]", prop.Type) | ||
} else { | ||
convertedProperties[key] = g.convertMetadataType(prop.Type) | ||
} | ||
} | ||
|
||
return convertedProperties | ||
} | ||
|
||
func (g *PythonTypeGenerator) convertMetadataType(propertyType string) string { | ||
switch propertyType { | ||
case "string": | ||
return "str" | ||
default: | ||
return propertyType | ||
} | ||
} | ||
|
||
// Utils | ||
func (g *PythonTypeGenerator) generateInterfaceName(action string) string { | ||
parts := strings.Split(action, ".") | ||
var name string | ||
for _, part := range parts { | ||
part = strings.ReplaceAll(part, "_", " ") | ||
words := strings.Fields(part) | ||
for _, word := range words { | ||
name += cases.Title(language.English).String(word) | ||
} | ||
} | ||
|
||
return name + "AuditLogEvent" | ||
} | ||
|
||
func (g *PythonTypeGenerator) generateUnionType(typeName string, values []string) error { | ||
quotedValues := make([]string, len(values)) | ||
for i, v := range values { | ||
quotedValues[i] = fmt.Sprintf("'%s'", v) | ||
} | ||
|
||
output := fmt.Sprintf("%s = Literal[%s]\n", typeName, strings.Join(quotedValues, ", ")) | ||
_, err := fmt.Fprint(g.writer, output) | ||
|
||
return err | ||
} |
Oops, something went wrong.