From 8b24b717b3bff14b83fc5b881d5ba6119105bc14 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 24 Apr 2024 16:01:02 +0100 Subject: [PATCH 01/15] chore: remove unneccessary InputType Signed-off-by: Justin Chadwell --- dagql/types.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/dagql/types.go b/dagql/types.go index cd226353164..7d3d4ba9ae1 100644 --- a/dagql/types.go +++ b/dagql/types.go @@ -876,9 +876,6 @@ func (e *EnumValues[T]) Install(srv *Server) { srv.scalars[zero.Type().Name()] = e } -// InputType represents a GraphQL Input Object type. -type InputType[T Type] struct{} - func MustInputSpec(val Type) InputObjectSpec { spec := InputObjectSpec{ Name: val.TypeName(), From f6e5eef4597ecef0275adbffef1c3b9b33cabeba Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Tue, 30 Apr 2024 11:22:35 +0100 Subject: [PATCH 02/15] chore: format mod typedef query Signed-off-by: Justin Chadwell --- cmd/dagger/module.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/dagger/module.go b/cmd/dagger/module.go index 102765499af..b5db6ed131b 100644 --- a/cmd/dagger/module.go +++ b/cmd/dagger/module.go @@ -744,27 +744,27 @@ fragment TypeDefRefParts on TypeDef { kind optional asObject { - name + name } asInterface { - name + name } asInput { - name + name } asList { - elementTypeDef { - kind - asObject { - name - } - asInterface { - name - } - asInput { - name - } + elementTypeDef { + kind + asObject { + name + } + asInterface { + name } + asInput { + name + } + } } } From 5c6a36b8c4899d994a0a16ba2127fa9793ac120a Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 25 Apr 2024 12:01:09 +0100 Subject: [PATCH 03/15] chore: avoid ModuleObjectType typed nil Signed-off-by: Justin Chadwell --- core/object.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/object.go b/core/object.go index ce9baf219fe..2ec44d3e9ad 100644 --- a/core/object.go +++ b/core/object.go @@ -18,6 +18,9 @@ type ModuleObjectType struct { } func (t *ModuleObjectType) SourceMod() Mod { + if t.mod == nil { + return nil + } return t.mod } From 23fec78d0359342ab94a5545ada92cae4a394114 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 24 Apr 2024 16:00:24 +0100 Subject: [PATCH 04/15] feat: add scalar typedefs to propagate scalars Signed-off-by: Justin Chadwell --- .../generator/go/templates/module_types.go | 18 +- cmd/dagger/flags.go | 22 +++ cmd/dagger/module.go | 11 ++ core/integration/module_call_test.go | 74 ++++++++ core/integration/module_test.go | 43 +++++ core/module.go | 15 ++ core/schema/coremod.go | 56 +++++- core/schema/module.go | 14 ++ core/typedef.go | 51 ++++++ core/typedef_test.go | 6 + dagql/types.go | 69 +++++++ docs/docs-graphql/schema.graphqls | 36 ++++ sdk/elixir/lib/dagger/gen/client.ex | 12 ++ sdk/elixir/lib/dagger/gen/scalar_type_def.ex | 48 +++++ .../lib/dagger/gen/scalar_type_def_id.ex | 6 + sdk/elixir/lib/dagger/gen/type_def.ex | 27 +++ sdk/elixir/lib/dagger/gen/type_def_kind.ex | 5 + sdk/go/dag/dag.gen.go | 6 + sdk/go/dagger.gen.go | 141 ++++++++++++++ sdk/php/generated/Client.php | 10 + sdk/php/generated/ScalarTypeDef.php | 51 ++++++ sdk/php/generated/ScalarTypeDefId.php | 16 ++ sdk/php/generated/TypeDef.php | 22 +++ sdk/php/generated/TypeDefKind.php | 3 + sdk/python/src/dagger/client/gen.py | 133 ++++++++++++++ sdk/rust/crates/dagger-sdk/src/gen.rs | 112 ++++++++++++ sdk/typescript/api/client.gen.ts | 173 ++++++++++++++++++ 27 files changed, 1174 insertions(+), 6 deletions(-) create mode 100644 sdk/elixir/lib/dagger/gen/scalar_type_def.ex create mode 100644 sdk/elixir/lib/dagger/gen/scalar_type_def_id.ex create mode 100644 sdk/php/generated/ScalarTypeDef.php create mode 100644 sdk/php/generated/ScalarTypeDefId.php diff --git a/cmd/codegen/generator/go/templates/module_types.go b/cmd/codegen/generator/go/templates/module_types.go index cc7d0ee5bea..9a4bc17e5ea 100644 --- a/cmd/codegen/generator/go/templates/module_types.go +++ b/cmd/codegen/generator/go/templates/module_types.go @@ -69,6 +69,10 @@ func (ps *parseState) parseGoTypeReference(typ types.Type, named *types.Named, i } parsedType := &parsedPrimitiveType{goType: t, isPtr: isPtr} if named != nil { + if ps.isDaggerGenerated(named.Obj()) { + // only pre-generated scalars allowed here + parsedType.scalarType = named + } parsedType.alias = named.Obj().Name() } return parsedType, nil @@ -120,6 +124,8 @@ type parsedPrimitiveType struct { goType *types.Basic isPtr bool + scalarType *types.Named + // if this is something like `type Foo string`, then alias will be "Foo" alias string } @@ -138,9 +144,12 @@ func (spec *parsedPrimitiveType) TypeDefCode() (*Statement, error) { default: return nil, fmt.Errorf("unsupported basic type: %+v", spec.goType) } - def := Qual("dag", "TypeDef").Call().Dot("WithKind").Call( - kind, - ) + var def *Statement + if spec.scalarType != nil { + def = Qual("dag", "TypeDef").Call().Dot("WithScalar").Call(Lit(spec.scalarType.Obj().Name())) + } else { + def = Qual("dag", "TypeDef").Call().Dot("WithKind").Call(kind) + } if spec.isPtr { def = def.Dot("WithOptional").Call(Lit(true)) } @@ -152,6 +161,9 @@ func (spec *parsedPrimitiveType) GoType() types.Type { } func (spec *parsedPrimitiveType) GoSubTypes() []types.Type { + if spec.scalarType != nil { + return []types.Type{spec.scalarType} + } return nil } diff --git a/cmd/dagger/flags.go b/cmd/dagger/flags.go index c823445bcb5..9d68fa89b0f 100644 --- a/cmd/dagger/flags.go +++ b/cmd/dagger/flags.go @@ -615,6 +615,17 @@ func (r *modFunctionArg) AddFlag(flags *pflag.FlagSet) (any, error) { val, _ := getDefaultValue[bool](r) return flags.Bool(name, val, usage), nil + case dagger.ScalarKind: + scalarName := r.TypeDef.AsScalar.Name + + if val := GetCustomFlagValue(scalarName); val != nil { + flags.Var(val, name, usage) + return val, nil + } + + val, _ := getDefaultValue[string](r) + return flags.String(name, val, usage), nil + case dagger.ObjectKind: objName := r.TypeDef.AsObject.Name @@ -653,6 +664,17 @@ func (r *modFunctionArg) AddFlag(flags *pflag.FlagSet) (any, error) { val, _ := getDefaultValue[[]bool](r) return flags.BoolSlice(name, val, usage), nil + case dagger.ScalarKind: + scalarName := r.TypeDef.AsScalar.Name + + if val := GetCustomFlagValueSlice(scalarName); val != nil { + flags.Var(val, name, usage) + return val, nil + } + + val, _ := getDefaultValue[[]string](r) + return flags.StringSlice(name, val, usage), nil + case dagger.ObjectKind: objName := elementType.AsObject.Name diff --git a/cmd/dagger/module.go b/cmd/dagger/module.go index b5db6ed131b..5c86479c41e 100644 --- a/cmd/dagger/module.go +++ b/cmd/dagger/module.go @@ -766,6 +766,9 @@ fragment TypeDefRefParts on TypeDef { } } } + asScalar { + name + } } fragment FunctionParts on Function { @@ -809,6 +812,9 @@ query TypeDefs { ...FieldParts } } + asScalar { + name + } asInterface { name sourceModuleName @@ -987,6 +993,7 @@ type modTypeDef struct { AsInterface *modInterface AsInput *modInput AsList *modList + AsScalar *modScalar } type functionProvider interface { @@ -1089,6 +1096,10 @@ func (o *modInterface) GetFunction(name string) (*modFunction, error) { return nil, fmt.Errorf("no function '%s' in interface type '%s'", name, o.Name) } +type modScalar struct { + Name string +} + type modInput struct { Name string Fields []*modField diff --git a/core/integration/module_call_test.go b/core/integration/module_call_test.go index dce18d0ac51..4a5003ffa85 100644 --- a/core/integration/module_call_test.go +++ b/core/integration/module_call_test.go @@ -499,6 +499,80 @@ func (m *Test) Cacher(ctx context.Context, cache *CacheVolume, val string) (stri require.Equal(t, "foo\nbar\n", out) }) + t.Run("platform args", func(t *testing.T) { + t.Parallel() + + c, ctx := connect(t) + + modGen := c.Container().From(golangImage). + WithMountedFile(testCLIBinPath, daggerCliFile(t, c)). + WithWorkdir("/work"). + With(daggerExec("init", "--source=.", "--name=test", "--sdk=go")). + WithNewFile("main.go", dagger.ContainerWithNewFileOpts{ + Contents: `package main + +type Test struct {} + +func (m *Test) FromPlatform(platform Platform) string { + return string(platform) +} + +func (m *Test) ToPlatform(platform string) Platform { + return Platform(platform) +} +`, + }) + + out, err := modGen.With(daggerCall("from-platform", "--platform", "linux/amd64")).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "linux/amd64", out) + _, err = modGen.With(daggerCall("from-platform", "--platform", "invalid")).Stdout(ctx) + require.ErrorContains(t, err, "unknown operating system or architecture") + + out, err = modGen.With(daggerCall("to-platform", "--platform", "linux/amd64")).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "linux/amd64", out) + _, err = modGen.With(daggerCall("from-platform", "--platform", "invalid")).Stdout(ctx) + require.ErrorContains(t, err, "unknown operating system or architecture") + }) + + t.Run("enum args", func(t *testing.T) { + t.Parallel() + + c, ctx := connect(t) + + modGen := c.Container().From(golangImage). + WithMountedFile(testCLIBinPath, daggerCliFile(t, c)). + WithWorkdir("/work"). + With(daggerExec("init", "--source=.", "--name=test", "--sdk=go")). + WithNewFile("main.go", dagger.ContainerWithNewFileOpts{ + Contents: `package main + +type Test struct {} + +func (m *Test) FromProto(proto NetworkProtocol) string { + return string(proto) +} + +func (m *Test) ToProto(proto string) NetworkProtocol { + return NetworkProtocol(proto) +} +`, + }) + + out, err := modGen.With(daggerCall("from-proto", "--proto", "TCP")).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "TCP", out) + _, err = modGen.With(daggerCall("from-proto", "--proto", "INVALID")).Stdout(ctx) + require.ErrorContains(t, err, "invalid enum value") + + out, err = modGen.With(daggerCall("to-proto", "--proto", "TCP")).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "TCP", out) + _, err = modGen.With(daggerCall("to-proto", "--proto", "INVALID")).Stdout(ctx) + require.ErrorContains(t, err, "invalid enum value") + }) + t.Run("module args", func(t *testing.T) { t.Parallel() diff --git a/core/integration/module_test.go b/core/integration/module_test.go index 174586a2824..a0059526d8e 100644 --- a/core/integration/module_test.go +++ b/core/integration/module_test.go @@ -2886,6 +2886,49 @@ class Foo { } } +func TestModuleScalarType(t *testing.T) { + // Verify use of a core scalar as an argument type. + + t.Parallel() + + type testCase struct { + sdk string + source string + } + for _, tc := range []testCase{ + { + sdk: "go", + source: `package main + +type Foo struct{} + +func (m *Foo) SayHello(platform Platform) string { + return "hello " + string(platform) +}`, + }, + } { + tc := tc + + t.Run(tc.sdk, func(t *testing.T) { + t.Parallel() + c, ctx := connect(t) + + modGen := c.Container().From(golangImage). + WithMountedFile(testCLIBinPath, daggerCliFile(t, c)). + WithWorkdir("/work"). + With(daggerExec("init", "--name=foo", "--sdk="+tc.sdk)). + With(sdkSource(tc.sdk, tc.source)) + + out, err := modGen.With(daggerQuery(`{foo{sayHello(platform: "linux/amd64")}}`)).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "hello linux/amd64", gjson.Get(out, "foo.sayHello").String()) + + _, err = modGen.With(daggerQuery(`{foo{sayHello(platform: "invalid")}}`)).Stdout(ctx) + require.ErrorContains(t, err, "unknown operating system or architecture") + }) + } +} + func TestModuleConflictingSameNameDeps(t *testing.T) { // A -> B -> Dint // A -> C -> Dstr diff --git a/core/module.go b/core/module.go index 8fe321c0179..7d226f40efd 100644 --- a/core/module.go +++ b/core/module.go @@ -317,6 +317,21 @@ func (mod *Module) ModTypeFor(ctx context.Context, typeDef *TypeDef, checkDirect return nil, false, nil } + case TypeDefKindScalar: + if checkDirectDeps { + // check to see if this is from a *direct* dependency + depType, ok, err := mod.Deps.ModTypeFor(ctx, typeDef) + if err != nil { + return nil, false, fmt.Errorf("failed to get type from dependency: %w", err) + } + if ok { + return depType, true, nil + } + } + + slog.ExtraDebug("module did not find scalar", "mod", mod.Name(), "scalar", typeDef.AsScalar.Value.Name) + return nil, false, nil + default: return nil, false, fmt.Errorf("unexpected type def kind %s", typeDef.Kind) } diff --git a/core/schema/coremod.go b/core/schema/coremod.go index 3f6af6f2bfe..adf321c6b52 100644 --- a/core/schema/coremod.go +++ b/core/schema/coremod.go @@ -71,12 +71,19 @@ func (m *CoreMod) ModTypeFor(ctx context.Context, typeDef *core.TypeDef, checkDi Underlying: underlyingType, } + case core.TypeDefKindScalar: + _, ok := m.Dag.ScalarType(typeDef.AsScalar.Value.Name) + if !ok { + return nil, false, nil + } + modType = &CoreModScalar{coreMod: m, name: typeDef.AsScalar.Value.Name} + case core.TypeDefKindObject: _, ok := m.Dag.ObjectType(typeDef.AsObject.Value.Name) if !ok { return nil, false, nil } - modType = &CoreModObject{coreMod: m} + modType = &CoreModObject{coreMod: m, name: typeDef.AsObject.Value.Name} case core.TypeDefKindInterface: // core does not yet defined any interfaces @@ -204,6 +211,48 @@ func (m *CoreMod) TypeDefs(ctx context.Context) ([]*core.TypeDef, error) { return typeDefs, nil } +// CoreModScalar represents scalars from core (Platform, etc) +type CoreModScalar struct { + coreMod *CoreMod + name string +} + +var _ core.ModType = (*CoreModScalar)(nil) + +func (obj *CoreModScalar) ConvertFromSDKResult(ctx context.Context, value any) (dagql.Typed, error) { + s, ok := obj.coreMod.Dag.ScalarType(obj.name) + if !ok { + return nil, fmt.Errorf("CoreModScalar.ConvertFromSDKResult: found no scalar type") + } + return s.DecodeInput(value) +} + +func (obj *CoreModScalar) ConvertToSDKInput(ctx context.Context, value dagql.Typed) (any, error) { + s, ok := obj.coreMod.Dag.ScalarType(obj.name) + if !ok { + return nil, fmt.Errorf("CoreModScalar.ConvertToSDKInput: found no scalar type") + } + val, ok := value.(dagql.Scalar[dagql.String]) + if !ok { + // we assume all core scalars are strings + return nil, fmt.Errorf("CoreModScalar.ConvertToSDKInput: core scalar should be string") + } + return s.DecodeInput(string(val.Value)) +} + +func (obj *CoreModScalar) SourceMod() core.Mod { + return obj.coreMod +} + +func (obj *CoreModScalar) TypeDef() *core.TypeDef { + return &core.TypeDef{ + Kind: core.TypeDefKindScalar, + AsScalar: dagql.NonNull(&core.ScalarTypeDef{ + Name: obj.name, + }), + } +} + // CoreModObject represents objects from core (Container, Directory, etc.) type CoreModObject struct { coreMod *CoreMod @@ -291,8 +340,9 @@ func introspectionRefToTypeDef(introspectionType *introspection.TypeRef, nonNull case string(introspection.ScalarBoolean): typeDef.Kind = core.TypeDefKindBoolean default: - // default to saying it's a string for now - typeDef.Kind = core.TypeDefKindString + // assume that all core scalars are strings + typeDef.Kind = core.TypeDefKindScalar + typeDef.AsScalar = dagql.NonNull(core.NewScalarTypeDef(introspectionType.Name, "")) } return typeDef, true, nil diff --git a/core/schema/module.go b/core/schema/module.go index dbbc2a1c23f..368bf26caaf 100644 --- a/core/schema/module.go +++ b/core/schema/module.go @@ -254,6 +254,9 @@ func (s *moduleSchema) Install() { dagql.Func("withKind", s.typeDefWithKind). Doc(`Sets the kind of the type.`), + dagql.Func("withScalar", s.typeDefWithScalar). + Doc(`Returns a TypeDef of kind Scalar with the provided name.`), + dagql.Func("withListOf", s.typeDefWithListOf). Doc(`Returns a TypeDef of kind List with the provided type for its elements.`), @@ -284,6 +287,7 @@ func (s *moduleSchema) Install() { dagql.Fields[*core.InputTypeDef]{}.Install(s.dag) dagql.Fields[*core.FieldTypeDef]{}.Install(s.dag) dagql.Fields[*core.ListTypeDef]{}.Install(s.dag) + dagql.Fields[*core.ScalarTypeDef]{}.Install(s.dag) dagql.Fields[*core.GeneratedCode]{ dagql.Func("withVCSGeneratedPaths", s.generatedCodeWithVCSGeneratedPaths). @@ -309,6 +313,16 @@ func (s *moduleSchema) typeDefWithKind(ctx context.Context, def *core.TypeDef, a return def.WithKind(args.Kind), nil } +func (s *moduleSchema) typeDefWithScalar(ctx context.Context, def *core.TypeDef, args struct { + Name string + Description string `default:""` +}) (*core.TypeDef, error) { + if args.Name == "" { + return nil, fmt.Errorf("scalar type def must have a name") + } + return def.WithScalar(args.Name, args.Description), nil +} + func (s *moduleSchema) typeDefWithListOf(ctx context.Context, def *core.TypeDef, args struct { ElementType core.TypeDefID }) (*core.TypeDef, error) { diff --git a/core/typedef.go b/core/typedef.go index 3b67e9689c7..56b9b1812b5 100644 --- a/core/typedef.go +++ b/core/typedef.go @@ -284,6 +284,7 @@ type TypeDef struct { AsObject dagql.Nullable[*ObjectTypeDef] `field:"true" doc:"If kind is OBJECT, the object-specific type definition. If kind is not OBJECT, this will be null."` AsInterface dagql.Nullable[*InterfaceTypeDef] `field:"true" doc:"If kind is INTERFACE, the interface-specific type definition. If kind is not INTERFACE, this will be null."` AsInput dagql.Nullable[*InputTypeDef] `field:"true" doc:"If kind is INPUT, the input-specific type definition. If kind is not INPUT, this will be null."` + AsScalar dagql.Nullable[*ScalarTypeDef] `field:"true" doc:"If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null."` } func (typeDef TypeDef) Clone() *TypeDef { @@ -300,6 +301,9 @@ func (typeDef TypeDef) Clone() *TypeDef { if typeDef.AsInput.Valid { cp.AsInput.Value = typeDef.AsInput.Value.Clone() } + if typeDef.AsScalar.Valid { + cp.AsScalar.Value = typeDef.AsScalar.Value.Clone() + } return &cp } @@ -323,6 +327,8 @@ func (typeDef *TypeDef) ToTyped() dagql.Typed { typed = dagql.Int(0) case TypeDefKindBoolean: typed = dagql.Boolean(false) + case TypeDefKindScalar: + typed = dagql.NewScalar[dagql.String](typeDef.AsScalar.Value.Name, dagql.String("")) case TypeDefKindList: typed = dagql.DynamicArrayOutput{Elem: typeDef.AsList.Value.ElementTypeDef.ToTyped()} case TypeDefKindObject: @@ -351,6 +357,8 @@ func (typeDef *TypeDef) ToInput() dagql.Input { typed = dagql.Int(0) case TypeDefKindBoolean: typed = dagql.Boolean(false) + case TypeDefKindScalar: + typed = dagql.NewScalar[dagql.String](typeDef.AsScalar.Value.Name, dagql.String("")) case TypeDefKindList: typed = dagql.DynamicArrayInput{ Elem: typeDef.AsList.Value.ElementTypeDef.ToInput(), @@ -389,6 +397,12 @@ func (typeDef *TypeDef) WithKind(kind TypeDefKind) *TypeDef { return typeDef } +func (typeDef *TypeDef) WithScalar(name string, desc string) *TypeDef { + typeDef = typeDef.WithKind(TypeDefKindScalar) + typeDef.AsScalar = dagql.NonNull(NewScalarTypeDef(name, desc)) + return typeDef +} + func (typeDef *TypeDef) WithListOf(elem *TypeDef) *TypeDef { typeDef = typeDef.WithKind(TypeDefKindList) typeDef.AsList = dagql.NonNull(&ListTypeDef{ @@ -470,6 +484,8 @@ func (typeDef *TypeDef) IsSubtypeOf(otherDef *TypeDef) bool { switch typeDef.Kind { case TypeDefKindString, TypeDefKindInteger, TypeDefKindBoolean, TypeDefKindVoid: return typeDef.Kind == otherDef.Kind + case TypeDefKindScalar: + return typeDef.AsScalar.Value.Name == otherDef.AsScalar.Value.Name case TypeDefKindList: if otherDef.Kind != TypeDefKindList { return false @@ -720,6 +736,39 @@ func (iface *InterfaceTypeDef) IsSubtypeOf(otherIface *InterfaceTypeDef) bool { return true } +type ScalarTypeDef struct { + Name string `field:"true" doc:"The name of the scalar."` + Description string `field:"true" doc:"A doc string for the scalar, if any."` + + OriginalName string + + // SourceModuleName is currently only set when returning the TypeDef from the Scalars field on Module + SourceModuleName string `field:"true" doc:"If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise."` +} + +func NewScalarTypeDef(name, description string) *ScalarTypeDef { + return &ScalarTypeDef{ + Name: strcase.ToCamel(name), + OriginalName: name, + Description: description, + } +} + +func (*ScalarTypeDef) Type() *ast.Type { + return &ast.Type{ + NamedType: "ScalarTypeDef", + NonNull: true, + } +} + +func (typeDef *ScalarTypeDef) TypeDescription() string { + return "A definition of a custom scalar defined in a Module." +} + +func (typeDef ScalarTypeDef) Clone() *ScalarTypeDef { + return &typeDef +} + type ListTypeDef struct { ElementTypeDef *TypeDef `field:"true" doc:"The type of the elements in the list."` } @@ -802,6 +851,8 @@ var ( "An integer value.") TypeDefKindBoolean = TypeDefKinds.Register("BOOLEAN_KIND", "A boolean value.") + TypeDefKindScalar = TypeDefKinds.Register("SCALAR_KIND", + "A scalar value of any basic kind.") TypeDefKindList = TypeDefKinds.Register("LIST_KIND", "A list of values all having the same type.", "Always paired with a ListTypeDef.") diff --git a/core/typedef_test.go b/core/typedef_test.go index 19ca450560b..07e71eb246d 100644 --- a/core/typedef_test.go +++ b/core/typedef_test.go @@ -19,6 +19,12 @@ var Samples = map[TypeDefKind]*TypeDef{ TypeDefKindBoolean: { Kind: TypeDefKindBoolean, }, + TypeDefKindScalar: { + Kind: TypeDefKindScalar, + AsScalar: dagql.NonNull(&ScalarTypeDef{ + Name: "FooScalar", + }), + }, TypeDefKindList: { Kind: TypeDefKindList, AsList: dagql.NonNull(&ListTypeDef{ diff --git a/dagql/types.go b/dagql/types.go index 7d3d4ba9ae1..e91743d74f0 100644 --- a/dagql/types.go +++ b/dagql/types.go @@ -471,6 +471,75 @@ func (s String) SetField(v reflect.Value) error { } } +type ScalarValue interface { + ScalarType + Input +} + +// Scalar is a GraphQL scalar. +type Scalar[T ScalarValue] struct { + Name string + Value T +} + +func NewScalar[T ScalarValue](name string, val T) Scalar[T] { + return Scalar[T]{name, val} +} + +var _ Typed = Scalar[ScalarValue]{} + +func (s Scalar[T]) Type() *ast.Type { + return &ast.Type{ + NamedType: s.Name, + NonNull: true, + } +} + +var _ ScalarValue = Scalar[ScalarValue]{} + +func (s Scalar[T]) TypeName() string { + return s.Name +} + +func (s Scalar[T]) TypeDefinition() *ast.Definition { + def := &ast.Definition{ + Kind: ast.Scalar, + Name: s.TypeName(), + } + var val T + if isType, ok := any(val).(Descriptive); ok { + def.Description = isType.TypeDescription() + } + return def +} + +func (s Scalar[T]) DecodeInput(val any) (Input, error) { + var empty T + input, err := empty.DecodeInput(val) + if err != nil { + return nil, err + } + return NewScalar[T](s.Name, input.(T)), nil +} + +var _ Input = Scalar[ScalarValue]{} + +func (s Scalar[T]) Decoder() InputDecoder { + return s +} + +func (s Scalar[T]) ToLiteral() call.Literal { + return s.Value.ToLiteral() +} + +func (s Scalar[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(s.Value) +} + +func (s *Scalar[T]) UnmarshalJSON(p []byte) error { + return json.Unmarshal(p, &s.Value) +} + // ID is a type-checked ID scalar. type ID[T Typed] struct { id *call.ID diff --git a/docs/docs-graphql/schema.graphqls b/docs/docs-graphql/schema.graphqls index 23a8e7c81f7..d9297fff637 100644 --- a/docs/docs-graphql/schema.graphqls +++ b/docs/docs-graphql/schema.graphqls @@ -2289,6 +2289,9 @@ type Query { """Load a Port from its ID.""" loadPortFromID(id: PortID!): Port! + """Load a ScalarTypeDef from its ID.""" + loadScalarTypeDefFromID(id: ScalarTypeDefID!): ScalarTypeDef! + """Load a Secret from its ID.""" loadSecretFromID(id: SecretID!): Secret! @@ -2370,6 +2373,28 @@ type Query { version: String! } +"""A definition of a custom scalar defined in a Module.""" +type ScalarTypeDef { + """A doc string for the scalar, if any.""" + description: String! + + """A unique identifier for this ScalarTypeDef.""" + id: ScalarTypeDefID! + + """The name of the scalar.""" + name: String! + + """ + If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise. + """ + sourceModuleName: String! +} + +""" +The `ScalarTypeDefID` scalar type represents an identifier for an object of type ScalarTypeDef. +""" +scalar ScalarTypeDefID + """ A reference to a secret value, which can be handled more safely than the value itself. """ @@ -2500,6 +2525,11 @@ type TypeDef { """ asObject: ObjectTypeDef + """ + If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null. + """ + asScalar: ScalarTypeDef + """A unique identifier for this TypeDef.""" id: TypeDefID! @@ -2555,6 +2585,9 @@ type TypeDef { """Sets whether this type can be set to null.""" withOptional(optional: Boolean!): TypeDef! + + """Returns a TypeDef of kind Scalar with the provided name.""" + withScalar(description: String = "", name: String!): TypeDef! } """ @@ -2573,6 +2606,9 @@ enum TypeDefKind { """A boolean value.""" BOOLEAN_KIND + """A scalar value of any basic kind.""" + SCALAR_KIND + """ A list of values all having the same type. diff --git a/sdk/elixir/lib/dagger/gen/client.ex b/sdk/elixir/lib/dagger/gen/client.ex index b5d8729f404..270499fd325 100644 --- a/sdk/elixir/lib/dagger/gen/client.ex +++ b/sdk/elixir/lib/dagger/gen/client.ex @@ -570,6 +570,18 @@ defmodule Dagger.Client do } end + @doc "Load a ScalarTypeDef from its ID." + @spec load_scalar_type_def_from_id(t(), Dagger.ScalarTypeDefID.t()) :: Dagger.ScalarTypeDef.t() + def load_scalar_type_def_from_id(%__MODULE__{} = client, id) do + selection = + client.selection |> select("loadScalarTypeDefFromID") |> put_arg("id", id) + + %Dagger.ScalarTypeDef{ + selection: selection, + client: client.client + } + end + @doc "Load a Secret from its ID." @spec load_secret_from_id(t(), Dagger.SecretID.t()) :: Dagger.Secret.t() def load_secret_from_id(%__MODULE__{} = client, id) do diff --git a/sdk/elixir/lib/dagger/gen/scalar_type_def.ex b/sdk/elixir/lib/dagger/gen/scalar_type_def.ex new file mode 100644 index 00000000000..0aa042aa20d --- /dev/null +++ b/sdk/elixir/lib/dagger/gen/scalar_type_def.ex @@ -0,0 +1,48 @@ +# This file generated by `dagger_codegen`. Please DO NOT EDIT. +defmodule Dagger.ScalarTypeDef do + @moduledoc "A definition of a custom scalar defined in a Module." + + use Dagger.Core.QueryBuilder + + @derive Dagger.ID + + defstruct [:selection, :client] + + @type t() :: %__MODULE__{} + + @doc "A doc string for the scalar, if any." + @spec description(t()) :: {:ok, String.t()} | {:error, term()} + def description(%__MODULE__{} = scalar_type_def) do + selection = + scalar_type_def.selection |> select("description") + + execute(selection, scalar_type_def.client) + end + + @doc "A unique identifier for this ScalarTypeDef." + @spec id(t()) :: {:ok, Dagger.ScalarTypeDefID.t()} | {:error, term()} + def id(%__MODULE__{} = scalar_type_def) do + selection = + scalar_type_def.selection |> select("id") + + execute(selection, scalar_type_def.client) + end + + @doc "The name of the scalar." + @spec name(t()) :: {:ok, String.t()} | {:error, term()} + def name(%__MODULE__{} = scalar_type_def) do + selection = + scalar_type_def.selection |> select("name") + + execute(selection, scalar_type_def.client) + end + + @doc "If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise." + @spec source_module_name(t()) :: {:ok, String.t()} | {:error, term()} + def source_module_name(%__MODULE__{} = scalar_type_def) do + selection = + scalar_type_def.selection |> select("sourceModuleName") + + execute(selection, scalar_type_def.client) + end +end diff --git a/sdk/elixir/lib/dagger/gen/scalar_type_def_id.ex b/sdk/elixir/lib/dagger/gen/scalar_type_def_id.ex new file mode 100644 index 00000000000..cb86e0ad9ca --- /dev/null +++ b/sdk/elixir/lib/dagger/gen/scalar_type_def_id.ex @@ -0,0 +1,6 @@ +# This file generated by `dagger_codegen`. Please DO NOT EDIT. +defmodule Dagger.ScalarTypeDefID do + @moduledoc "The `ScalarTypeDefID` scalar type represents an identifier for an object of type ScalarTypeDef." + + @type t() :: String.t() +end diff --git a/sdk/elixir/lib/dagger/gen/type_def.ex b/sdk/elixir/lib/dagger/gen/type_def.ex index 205b47f3e32..3d7c119cb6a 100644 --- a/sdk/elixir/lib/dagger/gen/type_def.ex +++ b/sdk/elixir/lib/dagger/gen/type_def.ex @@ -58,6 +58,18 @@ defmodule Dagger.TypeDef do } end + @doc "If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null." + @spec as_scalar(t()) :: Dagger.ScalarTypeDef.t() | nil + def as_scalar(%__MODULE__{} = type_def) do + selection = + type_def.selection |> select("asScalar") + + %Dagger.ScalarTypeDef{ + selection: selection, + client: type_def.client + } + end + @doc "A unique identifier for this TypeDef." @spec id(t()) :: {:ok, Dagger.TypeDefID.t()} | {:error, term()} def id(%__MODULE__{} = type_def) do @@ -199,4 +211,19 @@ defmodule Dagger.TypeDef do client: type_def.client } end + + @doc "Returns a TypeDef of kind Scalar with the provided name." + @spec with_scalar(t(), String.t(), [{:description, String.t() | nil}]) :: Dagger.TypeDef.t() + def with_scalar(%__MODULE__{} = type_def, name, optional_args \\ []) do + selection = + type_def.selection + |> select("withScalar") + |> put_arg("name", name) + |> maybe_put_arg("description", optional_args[:description]) + + %Dagger.TypeDef{ + selection: selection, + client: type_def.client + } + end end diff --git a/sdk/elixir/lib/dagger/gen/type_def_kind.ex b/sdk/elixir/lib/dagger/gen/type_def_kind.ex index 27b2b17634b..cad15f917ac 100644 --- a/sdk/elixir/lib/dagger/gen/type_def_kind.ex +++ b/sdk/elixir/lib/dagger/gen/type_def_kind.ex @@ -6,6 +6,7 @@ defmodule Dagger.TypeDefKind do :STRING_KIND | :INTEGER_KIND | :BOOLEAN_KIND + | :SCALAR_KIND | :LIST_KIND | :OBJECT_KIND | :INTERFACE_KIND @@ -24,6 +25,10 @@ defmodule Dagger.TypeDefKind do @spec boolean_kind() :: :BOOLEAN_KIND def boolean_kind(), do: :BOOLEAN_KIND + @doc "A scalar value of any basic kind." + @spec scalar_kind() :: :SCALAR_KIND + def scalar_kind(), do: :SCALAR_KIND + @doc """ A list of values all having the same type. diff --git a/sdk/go/dag/dag.gen.go b/sdk/go/dag/dag.gen.go index b89cdd7ac89..c3baa27d34a 100644 --- a/sdk/go/dag/dag.gen.go +++ b/sdk/go/dag/dag.gen.go @@ -302,6 +302,12 @@ func LoadPortFromID(id dagger.PortID) *dagger.Port { return client.LoadPortFromID(id) } +// Load a ScalarTypeDef from its ID. +func LoadScalarTypeDefFromID(id dagger.ScalarTypeDefID) *dagger.ScalarTypeDef { + client := initClient() + return client.LoadScalarTypeDefFromID(id) +} + // Load a Secret from its ID. func LoadSecretFromID(id dagger.SecretID) *dagger.Secret { client := initClient() diff --git a/sdk/go/dagger.gen.go b/sdk/go/dagger.gen.go index 0600bcce4a1..3dcc03fa2cc 100644 --- a/sdk/go/dagger.gen.go +++ b/sdk/go/dagger.gen.go @@ -198,6 +198,9 @@ type Platform string // The `PortID` scalar type represents an identifier for an object of type Port. type PortID string +// The `ScalarTypeDefID` scalar type represents an identifier for an object of type ScalarTypeDef. +type ScalarTypeDefID string + // The `SecretID` scalar type represents an identifier for an object of type Secret. type SecretID string @@ -5853,6 +5856,16 @@ func (r *Client) LoadPortFromID(id PortID) *Port { } } +// Load a ScalarTypeDef from its ID. +func (r *Client) LoadScalarTypeDefFromID(id ScalarTypeDefID) *ScalarTypeDef { + q := r.query.Select("loadScalarTypeDefFromID") + q = q.Arg("id", id) + + return &ScalarTypeDef{ + query: q, + } +} + // Load a Secret from its ID. func (r *Client) LoadSecretFromID(id SecretID) *Secret { q := r.query.Select("loadSecretFromID") @@ -6051,6 +6064,101 @@ func (r *Client) Version(ctx context.Context) (string, error) { return response, q.Execute(ctx) } +// A definition of a custom scalar defined in a Module. +type ScalarTypeDef struct { + query *querybuilder.Selection + + description *string + id *ScalarTypeDefID + name *string + sourceModuleName *string +} + +func (r *ScalarTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *ScalarTypeDef { + return &ScalarTypeDef{ + query: q, + } +} + +// A doc string for the scalar, if any. +func (r *ScalarTypeDef) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this ScalarTypeDef. +func (r *ScalarTypeDef) ID(ctx context.Context) (ScalarTypeDefID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ScalarTypeDefID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ScalarTypeDef) XXX_GraphQLType() string { + return "ScalarTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ScalarTypeDef) XXX_GraphQLIDType() string { + return "ScalarTypeDefID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ScalarTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ScalarTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} + +// The name of the scalar. +func (r *ScalarTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise. +func (r *ScalarTypeDef) SourceModuleName(ctx context.Context) (string, error) { + if r.sourceModuleName != nil { + return *r.sourceModuleName, nil + } + q := r.query.Select("sourceModuleName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // A reference to a secret value, which can be handled more safely than the value itself. type Secret struct { query *querybuilder.Selection @@ -6511,6 +6619,15 @@ func (r *TypeDef) AsObject() *ObjectTypeDef { } } +// If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null. +func (r *TypeDef) AsScalar() *ScalarTypeDef { + q := r.query.Select("asScalar") + + return &ScalarTypeDef{ + query: q, + } +} + // A unique identifier for this TypeDef. func (r *TypeDef) ID(ctx context.Context) (TypeDefID, error) { if r.id != nil { @@ -6698,6 +6815,27 @@ func (r *TypeDef) WithOptional(optional bool) *TypeDef { } } +// TypeDefWithScalarOpts contains options for TypeDef.WithScalar +type TypeDefWithScalarOpts struct { + Description string +} + +// Returns a TypeDef of kind Scalar with the provided name. +func (r *TypeDef) WithScalar(name string, opts ...TypeDefWithScalarOpts) *TypeDef { + q := r.query.Select("withScalar") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + } + q = q.Arg("name", name) + + return &TypeDef{ + query: q, + } +} + type CacheSharingMode string func (CacheSharingMode) IsEnum() {} @@ -6786,6 +6924,9 @@ const ( // Always paired with an ObjectTypeDef. ObjectKind TypeDefKind = "OBJECT_KIND" + // A scalar value of any basic kind. + ScalarKind TypeDefKind = "SCALAR_KIND" + // A string value. StringKind TypeDefKind = "STRING_KIND" diff --git a/sdk/php/generated/Client.php b/sdk/php/generated/Client.php index a0867a8518d..54ebf0adb25 100644 --- a/sdk/php/generated/Client.php +++ b/sdk/php/generated/Client.php @@ -473,6 +473,16 @@ public function loadPortFromID(PortId|Port $id): Port return new \Dagger\Port($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); } + /** + * Load a ScalarTypeDef from its ID. + */ + public function loadScalarTypeDefFromID(ScalarTypeDefId|ScalarTypeDef $id): ScalarTypeDef + { + $innerQueryBuilder = new \Dagger\Client\QueryBuilder('loadScalarTypeDefFromID'); + $innerQueryBuilder->setArgument('id', $id); + return new \Dagger\ScalarTypeDef($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); + } + /** * Load a Secret from its ID. */ diff --git a/sdk/php/generated/ScalarTypeDef.php b/sdk/php/generated/ScalarTypeDef.php new file mode 100644 index 00000000000..1e9c9d119c9 --- /dev/null +++ b/sdk/php/generated/ScalarTypeDef.php @@ -0,0 +1,51 @@ +queryLeaf($leafQueryBuilder, 'description'); + } + + /** + * A unique identifier for this ScalarTypeDef. + */ + public function id(): ScalarTypeDefId + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('id'); + return new \Dagger\ScalarTypeDefId((string)$this->queryLeaf($leafQueryBuilder, 'id')); + } + + /** + * The name of the scalar. + */ + public function name(): string + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('name'); + return (string)$this->queryLeaf($leafQueryBuilder, 'name'); + } + + /** + * If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise. + */ + public function sourceModuleName(): string + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('sourceModuleName'); + return (string)$this->queryLeaf($leafQueryBuilder, 'sourceModuleName'); + } +} diff --git a/sdk/php/generated/ScalarTypeDefId.php b/sdk/php/generated/ScalarTypeDefId.php new file mode 100644 index 00000000000..2e895798473 --- /dev/null +++ b/sdk/php/generated/ScalarTypeDefId.php @@ -0,0 +1,16 @@ +client, $this->queryBuilderChain->chain($innerQueryBuilder)); } + /** + * If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null. + */ + public function asScalar(): ScalarTypeDef + { + $innerQueryBuilder = new \Dagger\Client\QueryBuilder('asScalar'); + return new \Dagger\ScalarTypeDef($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); + } + /** * A unique identifier for this TypeDef. */ @@ -167,4 +176,17 @@ public function withOptional(bool $optional): TypeDef $innerQueryBuilder->setArgument('optional', $optional); return new \Dagger\TypeDef($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); } + + /** + * Returns a TypeDef of kind Scalar with the provided name. + */ + public function withScalar(string $name, ?string $description = ''): TypeDef + { + $innerQueryBuilder = new \Dagger\Client\QueryBuilder('withScalar'); + $innerQueryBuilder->setArgument('name', $name); + if (null !== $description) { + $innerQueryBuilder->setArgument('description', $description); + } + return new \Dagger\TypeDef($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); + } } diff --git a/sdk/php/generated/TypeDefKind.php b/sdk/php/generated/TypeDefKind.php index 76b3e390945..f1e9f34adae 100644 --- a/sdk/php/generated/TypeDefKind.php +++ b/sdk/php/generated/TypeDefKind.php @@ -22,6 +22,9 @@ enum TypeDefKind: string /** A boolean value. */ case BOOLEAN_KIND = 'BOOLEAN_KIND'; + /** A scalar value of any basic kind. */ + case SCALAR_KIND = 'SCALAR_KIND'; + /** * A list of values all having the same type. * diff --git a/sdk/python/src/dagger/client/gen.py b/sdk/python/src/dagger/client/gen.py index dd6611f4669..5586af6b118 100644 --- a/sdk/python/src/dagger/client/gen.py +++ b/sdk/python/src/dagger/client/gen.py @@ -156,6 +156,11 @@ class PortID(Scalar): type Port.""" +class ScalarTypeDefID(Scalar): + """The `ScalarTypeDefID` scalar type represents an identifier for an + object of type ScalarTypeDef.""" + + class SecretID(Scalar): """The `SecretID` scalar type represents an identifier for an object of type Secret.""" @@ -265,6 +270,9 @@ class TypeDefKind(Enum): Always paired with an ObjectTypeDef. """ + SCALAR_KIND = "SCALAR_KIND" + """A scalar value of any basic kind.""" + STRING_KIND = "STRING_KIND" """A string value.""" @@ -5869,6 +5877,14 @@ def load_port_from_id(self, id: PortID) -> Port: _ctx = self._select("loadPortFromID", _args) return Port(_ctx) + def load_scalar_type_def_from_id(self, id: ScalarTypeDefID) -> "ScalarTypeDef": + """Load a ScalarTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadScalarTypeDefFromID", _args) + return ScalarTypeDef(_ctx) + def load_secret_from_id(self, id: SecretID) -> "Secret": """Load a Secret from its ID.""" _args = [ @@ -6075,6 +6091,99 @@ def with_(self, cb: Callable[["Client"], "Client"]) -> "Client": return cb(self) +@typecheck +class ScalarTypeDef(Type): + """A definition of a custom scalar defined in a Module.""" + + async def description(self) -> str: + """A doc string for the scalar, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def id(self) -> ScalarTypeDefID: + """A unique identifier for this ScalarTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ScalarTypeDefID + The `ScalarTypeDefID` scalar type represents an identifier for an + object of type ScalarTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ScalarTypeDefID) + + async def name(self) -> str: + """The name of the scalar. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def source_module_name(self) -> str: + """If this ScalarTypeDef is associated with a Module, the name of the + module. Unset otherwise. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("sourceModuleName", _args) + return await _ctx.execute(str) + + @typecheck class Secret(Type): """A reference to a secret value, which can be handled more safely @@ -6456,6 +6565,14 @@ def as_object(self) -> ObjectTypeDef: _ctx = self._select("asObject", _args) return ObjectTypeDef(_ctx) + def as_scalar(self) -> ScalarTypeDef: + """If kind is SCALAR, the scalar-specific type definition. If kind is not + SCALAR, this will be null. + """ + _args: list[Arg] = [] + _ctx = self._select("asScalar", _args) + return ScalarTypeDef(_ctx) + async def id(self) -> TypeDefID: """A unique identifier for this TypeDef. @@ -6624,6 +6741,20 @@ def with_optional(self, optional: bool) -> Self: _ctx = self._select("withOptional", _args) return TypeDef(_ctx) + def with_scalar( + self, + name: str, + *, + description: str | None = "", + ) -> Self: + """Returns a TypeDef of kind Scalar with the provided name.""" + _args = [ + Arg("name", name), + Arg("description", description, ""), + ] + _ctx = self._select("withScalar", _args) + return TypeDef(_ctx) + def with_(self, cb: Callable[["TypeDef"], "TypeDef"]) -> "TypeDef": """Call the provided callable with current TypeDef. @@ -6701,6 +6832,8 @@ def with_(self, cb: Callable[["TypeDef"], "TypeDef"]) -> "TypeDef": "Port", "PortForward", "PortID", + "ScalarTypeDef", + "ScalarTypeDefID", "Secret", "SecretID", "Service", diff --git a/sdk/rust/crates/dagger-sdk/src/gen.rs b/sdk/rust/crates/dagger-sdk/src/gen.rs index 5472fb2e0e4..f0f6a85cda0 100644 --- a/sdk/rust/crates/dagger-sdk/src/gen.rs +++ b/sdk/rust/crates/dagger-sdk/src/gen.rs @@ -973,6 +973,23 @@ impl PortId { } } #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct ScalarTypeDefId(pub String); +impl Into for &str { + fn into(self) -> ScalarTypeDefId { + ScalarTypeDefId(self.to_string()) + } +} +impl Into for String { + fn into(self) -> ScalarTypeDefId { + ScalarTypeDefId(self.clone()) + } +} +impl ScalarTypeDefId { + fn quote(&self) -> String { + format!("\"{}\"", self.0.clone()) + } +} +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] pub struct SecretId(pub String); impl From<&str> for SecretId { fn from(value: &str) -> Self { @@ -6236,6 +6253,22 @@ impl Query { graphql_client: self.graphql_client.clone(), } } + /// Load a ScalarTypeDef from its ID. + pub fn load_scalar_type_def_from_id(&self, id: ScalarTypeDef) -> ScalarTypeDef { + let mut query = self.selection.select("loadScalarTypeDefFromID"); + query = query.arg_lazy( + "id", + Box::new(move || { + let id = id.clone(); + Box::pin(async move { id.id().await.unwrap().quote() }) + }), + ); + ScalarTypeDef { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } /// Load a Secret from its ID. pub fn load_secret_from_id(&self, id: impl IntoID) -> Secret { let mut query = self.selection.select("loadSecretFromID"); @@ -6527,6 +6560,34 @@ impl Query { } } #[derive(Clone)] +pub struct ScalarTypeDef { + pub proc: Option>, + pub selection: Selection, + pub graphql_client: DynGraphQLClient, +} +impl ScalarTypeDef { + /// A doc string for the scalar, if any. + pub async fn description(&self) -> Result { + let query = self.selection.select("description"); + query.execute(self.graphql_client.clone()).await + } + /// A unique identifier for this ScalarTypeDef. + pub async fn id(&self) -> Result { + let query = self.selection.select("id"); + query.execute(self.graphql_client.clone()).await + } + /// The name of the scalar. + pub async fn name(&self) -> Result { + let query = self.selection.select("name"); + query.execute(self.graphql_client.clone()).await + } + /// If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise. + pub async fn source_module_name(&self) -> Result { + let query = self.selection.select("sourceModuleName"); + query.execute(self.graphql_client.clone()).await + } +} +#[derive(Clone)] pub struct Secret { pub proc: Option>, pub selection: Selection, @@ -6736,6 +6797,11 @@ pub struct TypeDefWithObjectOpts<'a> { #[builder(setter(into, strip_option), default)] pub description: Option<&'a str>, } +#[derive(Builder, Debug, PartialEq)] +pub struct TypeDefWithScalarOpts<'a> { + #[builder(setter(into, strip_option), default)] + pub description: Option<&'a str>, +} impl TypeDef { /// If kind is INPUT, the input-specific type definition. If kind is not INPUT, this will be null. pub fn as_input(&self) -> InputTypeDef { @@ -6773,6 +6839,15 @@ impl TypeDef { graphql_client: self.graphql_client.clone(), } } + /// If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null. + pub fn as_scalar(&self) -> ScalarTypeDef { + let query = self.selection.select("asScalar"); + ScalarTypeDef { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } /// A unique identifier for this TypeDef. pub async fn id(&self) -> Result { let query = self.selection.select("id"); @@ -6982,6 +7057,41 @@ impl TypeDef { graphql_client: self.graphql_client.clone(), } } + /// Returns a TypeDef of kind Scalar with the provided name. + /// + /// # Arguments + /// + /// * `opt` - optional argument, see inner type for documentation, use _opts to use + pub fn with_scalar(&self, name: impl Into) -> TypeDef { + let mut query = self.selection.select("withScalar"); + query = query.arg("name", name.into()); + TypeDef { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } + /// Returns a TypeDef of kind Scalar with the provided name. + /// + /// # Arguments + /// + /// * `opt` - optional argument, see inner type for documentation, use _opts to use + pub fn with_scalar_opts<'a>( + &self, + name: impl Into, + opts: TypeDefWithScalarOpts<'a>, + ) -> TypeDef { + let mut query = self.selection.select("withScalar"); + query = query.arg("name", name.into()); + if let Some(description) = opts.description { + query = query.arg("description", description); + } + TypeDef { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } } #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum CacheSharingMode { @@ -7038,6 +7148,8 @@ pub enum TypeDefKind { ListKind, #[serde(rename = "OBJECT_KIND")] ObjectKind, + #[serde(rename = "SCALAR_KIND")] + ScalarKind, #[serde(rename = "STRING_KIND")] StringKind, #[serde(rename = "VOID_KIND")] diff --git a/sdk/typescript/api/client.gen.ts b/sdk/typescript/api/client.gen.ts index 88fde652553..2cf9f955530 100644 --- a/sdk/typescript/api/client.gen.ts +++ b/sdk/typescript/api/client.gen.ts @@ -942,6 +942,11 @@ export type ClientSecretOpts = { accessor?: string } +/** + * The `ScalarTypeDefID` scalar type represents an identifier for an object of type ScalarTypeDef. + */ +export type ScalarTypeDefID = string & { __ScalarTypeDefID: never } + /** * The `SecretID` scalar type represents an identifier for an object of type Secret. */ @@ -1010,6 +1015,10 @@ export type TypeDefWithObjectOpts = { description?: string } +export type TypeDefWithScalarOpts = { + description?: string +} + /** * The `TypeDefID` scalar type represents an identifier for an object of type TypeDef. */ @@ -1055,6 +1064,11 @@ export enum TypeDefKind { */ ObjectKind = "OBJECT_KIND", + /** + * A scalar value of any basic kind. + */ + ScalarKind = "SCALAR_KIND", + /** * A string value. */ @@ -7630,6 +7644,22 @@ export class Client extends BaseClient { }) } + /** + * Load a ScalarTypeDef from its ID. + */ + loadScalarTypeDefFromID = (id: ScalarTypeDefID): ScalarTypeDef => { + return new ScalarTypeDef({ + queryTree: [ + ...this._queryTree, + { + operation: "loadScalarTypeDefFromID", + args: { id }, + }, + ], + ctx: this._ctx, + }) + } + /** * Load a Secret from its ID. */ @@ -7881,6 +7911,118 @@ export class Client extends BaseClient { } } +/** + * A definition of a custom scalar defined in a Module. + */ +export class ScalarTypeDef extends BaseClient { + private readonly _id?: ScalarTypeDefID = undefined + private readonly _description?: string = undefined + private readonly _name?: string = undefined + private readonly _sourceModuleName?: string = undefined + + /** + * Constructor is used for internal usage only, do not create object from it. + */ + constructor( + parent?: { queryTree?: QueryTree[]; ctx: Context }, + _id?: ScalarTypeDefID, + _description?: string, + _name?: string, + _sourceModuleName?: string, + ) { + super(parent) + + this._id = _id + this._description = _description + this._name = _name + this._sourceModuleName = _sourceModuleName + } + + /** + * A unique identifier for this ScalarTypeDef. + */ + id = async (): Promise => { + if (this._id) { + return this._id + } + + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "id", + }, + ], + await this._ctx.connection(), + ) + + return response + } + + /** + * A doc string for the scalar, if any. + */ + description = async (): Promise => { + if (this._description) { + return this._description + } + + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "description", + }, + ], + await this._ctx.connection(), + ) + + return response + } + + /** + * The name of the scalar. + */ + name = async (): Promise => { + if (this._name) { + return this._name + } + + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "name", + }, + ], + await this._ctx.connection(), + ) + + return response + } + + /** + * If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise. + */ + sourceModuleName = async (): Promise => { + if (this._sourceModuleName) { + return this._sourceModuleName + } + + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "sourceModuleName", + }, + ], + await this._ctx.connection(), + ) + + return response + } +} + /** * A reference to a secret value, which can be handled more safely than the value itself. */ @@ -8384,6 +8526,21 @@ export class TypeDef extends BaseClient { }) } + /** + * If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null. + */ + asScalar = (): ScalarTypeDef => { + return new ScalarTypeDef({ + queryTree: [ + ...this._queryTree, + { + operation: "asScalar", + }, + ], + ctx: this._ctx, + }) + } + /** * The kind of type this is (e.g. primitive, list, object). */ @@ -8571,6 +8728,22 @@ export class TypeDef extends BaseClient { }) } + /** + * Returns a TypeDef of kind Scalar with the provided name. + */ + withScalar = (name: string, opts?: TypeDefWithScalarOpts): TypeDef => { + return new TypeDef({ + queryTree: [ + ...this._queryTree, + { + operation: "withScalar", + args: { name, ...opts }, + }, + ], + ctx: this._ctx, + }) + } + /** * Call the provided function with current TypeDef. * From 8948b512e5207e18aae571eb02575d6571e45756 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 24 Apr 2024 12:42:36 +0100 Subject: [PATCH 05/15] feat: interpret current arg to get current platform Signed-off-by: Justin Chadwell --- cmd/dagger/flags.go | 35 ++++++++++++++++++++++++++++ cmd/dagger/functions.go | 1 + core/integration/module_call_test.go | 4 ++++ 3 files changed, 40 insertions(+) diff --git a/cmd/dagger/flags.go b/cmd/dagger/flags.go index 9d68fa89b0f..5b832621e3a 100644 --- a/cmd/dagger/flags.go +++ b/cmd/dagger/flags.go @@ -17,6 +17,7 @@ import ( "strconv" "strings" + "github.com/containerd/containerd/platforms" "github.com/moby/buildkit/util/gitutil" "github.com/spf13/pflag" @@ -44,6 +45,8 @@ func GetCustomFlagValue(name string) DaggerValue { return &moduleSourceValue{} case Module: return &moduleValue{} + case Platform: + return &platformValue{} } return nil } @@ -69,6 +72,8 @@ func GetCustomFlagValueSlice(name string) DaggerValue { return &sliceValue[*moduleSourceValue]{} case Module: return &sliceValue[*moduleValue]{} + case Platform: + return &sliceValue[*platformValue]{} } return nil } @@ -592,6 +597,36 @@ func (v *moduleSourceValue) Get(ctx context.Context, dag *dagger.Client, _ *dagg return modConf.Source, nil } +type platformValue struct { + platform string +} + +func (v *platformValue) Type() string { + return Platform +} + +func (v *platformValue) Set(s string) error { + if s == "" { + return fmt.Errorf("platform cannot be empty") + } + if s == "current" { + s = platforms.DefaultString() + } + v.platform = s + return nil +} + +func (v *platformValue) String() string { + return v.platform +} + +func (v *platformValue) Get(ctx context.Context, dag *dagger.Client, _ *dagger.ModuleSource) (any, error) { + if v.platform == "" { + return nil, fmt.Errorf("platform cannot be empty") + } + return v.platform, nil +} + // AddFlag adds a flag appropriate for the argument type. Should return a // pointer to the value. func (r *modFunctionArg) AddFlag(flags *pflag.FlagSet) (any, error) { diff --git a/cmd/dagger/functions.go b/cmd/dagger/functions.go index 85843fc384c..83f20f5c989 100644 --- a/cmd/dagger/functions.go +++ b/cmd/dagger/functions.go @@ -30,6 +30,7 @@ const ( CacheVolume string = "CacheVolume" ModuleSource string = "ModuleSource" Module string = "Module" + Platform string = "Platform" ) var funcGroup = &cobra.Group{ diff --git a/core/integration/module_call_test.go b/core/integration/module_call_test.go index 4a5003ffa85..4bc46f909f5 100644 --- a/core/integration/module_call_test.go +++ b/core/integration/module_call_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/containerd/containerd/platforms" "github.com/moby/buildkit/identity" "github.com/stretchr/testify/require" @@ -526,6 +527,9 @@ func (m *Test) ToPlatform(platform string) Platform { out, err := modGen.With(daggerCall("from-platform", "--platform", "linux/amd64")).Stdout(ctx) require.NoError(t, err) require.Equal(t, "linux/amd64", out) + out, err = modGen.With(daggerCall("from-platform", "--platform", "current")).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, platforms.DefaultString(), out) _, err = modGen.With(daggerCall("from-platform", "--platform", "invalid")).Stdout(ctx) require.ErrorContains(t, err, "unknown operating system or architecture") From a89207826d1fe037142667129b81143771b5ecbb Mon Sep 17 00:00:00 2001 From: Helder Correia <174525+helderco@users.noreply.github.com> Date: Thu, 2 May 2024 11:00:03 +0000 Subject: [PATCH 06/15] Add Python fix Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com> --- core/integration/module_test.go | 29 +++++++++++++++---------- sdk/python/src/dagger/mod/_converter.py | 17 ++------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/core/integration/module_test.go b/core/integration/module_test.go index a0059526d8e..e815057dfcd 100644 --- a/core/integration/module_test.go +++ b/core/integration/module_test.go @@ -2900,30 +2900,37 @@ func TestModuleScalarType(t *testing.T) { sdk: "go", source: `package main -type Foo struct{} +type Test struct{} -func (m *Foo) SayHello(platform Platform) string { +func (m *Test) SayHello(platform Platform) string { return "hello " + string(platform) }`, }, + { + sdk: "python", + source: `import dagger +from dagger import function, object_type + +@object_type +class Test: + @function + def say_hello(self, platform: dagger.Platform) -> str: + return f"hello {platform}" +`, + }, } { tc := tc t.Run(tc.sdk, func(t *testing.T) { t.Parallel() c, ctx := connect(t) + modGen := modInit(t, c, tc.sdk, tc.source) - modGen := c.Container().From(golangImage). - WithMountedFile(testCLIBinPath, daggerCliFile(t, c)). - WithWorkdir("/work"). - With(daggerExec("init", "--name=foo", "--sdk="+tc.sdk)). - With(sdkSource(tc.sdk, tc.source)) - - out, err := modGen.With(daggerQuery(`{foo{sayHello(platform: "linux/amd64")}}`)).Stdout(ctx) + out, err := modGen.With(daggerQuery(`{test{sayHello(platform: "linux/amd64")}}`)).Stdout(ctx) require.NoError(t, err) - require.Equal(t, "hello linux/amd64", gjson.Get(out, "foo.sayHello").String()) + require.Equal(t, "hello linux/amd64", gjson.Get(out, "test.sayHello").String()) - _, err = modGen.With(daggerQuery(`{foo{sayHello(platform: "invalid")}}`)).Stdout(ctx) + _, err = modGen.With(daggerQuery(`{test{sayHello(platform: "invalid")}}`)).Stdout(ctx) require.ErrorContains(t, err, "unknown operating system or architecture") }) } diff --git a/sdk/python/src/dagger/mod/_converter.py b/sdk/python/src/dagger/mod/_converter.py index 652834eb256..b8c7c300435 100644 --- a/sdk/python/src/dagger/mod/_converter.py +++ b/sdk/python/src/dagger/mod/_converter.py @@ -107,21 +107,8 @@ def to_typedef(annotation: type) -> "TypeDef": # noqa: C901,PLR0912 if typ.hint in builtins: return td.with_kind(builtins[typ.hint]) - # TODO: Fix when we have support for TypeDefKind.ENUM_KIND in core. - if issubclass(typ.hint, Enum): - msg = ( - "Enum types are not supported yet. Define argument as a string" - " and convert to the desired enum type in the function body." - ) - raise NotImplementedError(msg) - - # TODO: Fix when we have support for TypeDefKind.SCALAR_KIND in core. - if issubclass(typ.hint, Scalar): - msg = ( - "Scalar types are not supported yet. Define argument as a string" - " and convert to the desired scalar type in the function body." - ) - raise NotImplementedError(msg) + if issubclass(cls := typ.hint, Scalar | Enum): + return td.with_scalar(cls.__name__, description=get_doc(cls)) # NB: str is a Collection, but we've handled it above. if typ.is_subhint(TypeHint(Collection)): From 336c2e257666bf490bc4b537194f0abeee01ff68 Mon Sep 17 00:00:00 2001 From: Helder Correia <174525+helderco@users.noreply.github.com> Date: Thu, 2 May 2024 16:31:22 +0000 Subject: [PATCH 07/15] Replace ignored stdout with sync Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com> --- core/integration/module_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/integration/module_test.go b/core/integration/module_test.go index e815057dfcd..2412bcb577b 100644 --- a/core/integration/module_test.go +++ b/core/integration/module_test.go @@ -2930,7 +2930,7 @@ class Test: require.NoError(t, err) require.Equal(t, "hello linux/amd64", gjson.Get(out, "test.sayHello").String()) - _, err = modGen.With(daggerQuery(`{test{sayHello(platform: "invalid")}}`)).Stdout(ctx) + _, err = modGen.With(daggerQuery(`{test{sayHello(platform: "invalid")}}`)).Sync(ctx) require.ErrorContains(t, err, "unknown operating system or architecture") }) } From 0bfdeb61f891dd84ee2c177716f7813d0bc3f6d4 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 2 May 2024 17:53:46 +0100 Subject: [PATCH 08/15] fix test to-platform Signed-off-by: Justin Chadwell --- core/integration/module_call_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/integration/module_call_test.go b/core/integration/module_call_test.go index 4bc46f909f5..5892ae83c2f 100644 --- a/core/integration/module_call_test.go +++ b/core/integration/module_call_test.go @@ -536,7 +536,7 @@ func (m *Test) ToPlatform(platform string) Platform { out, err = modGen.With(daggerCall("to-platform", "--platform", "linux/amd64")).Stdout(ctx) require.NoError(t, err) require.Equal(t, "linux/amd64", out) - _, err = modGen.With(daggerCall("from-platform", "--platform", "invalid")).Stdout(ctx) + _, err = modGen.With(daggerCall("to-platform", "--platform", "invalid")).Stdout(ctx) require.ErrorContains(t, err, "unknown operating system or architecture") }) From 97f30d531c3952a98a276d6b0d790812349a9bab Mon Sep 17 00:00:00 2001 From: Helder Correia <174525+helderco@users.noreply.github.com> Date: Thu, 2 May 2024 17:35:29 +0000 Subject: [PATCH 09/15] Test return values and enum types Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com> --- core/integration/module_test.go | 106 +++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/core/integration/module_test.go b/core/integration/module_test.go index 2412bcb577b..1444666cd35 100644 --- a/core/integration/module_test.go +++ b/core/integration/module_test.go @@ -2887,8 +2887,6 @@ class Foo { } func TestModuleScalarType(t *testing.T) { - // Verify use of a core scalar as an argument type. - t.Parallel() type testCase struct { @@ -2902,9 +2900,14 @@ func TestModuleScalarType(t *testing.T) { type Test struct{} -func (m *Test) SayHello(platform Platform) string { - return "hello " + string(platform) -}`, +func (m *Test) FromPlatform(platform Platform) string { + return string(platform) +} + +func (m *Test) ToPlatform(platform string) Platform { + return Platform(platform) +} +`, }, { sdk: "python", @@ -2914,8 +2917,12 @@ from dagger import function, object_type @object_type class Test: @function - def say_hello(self, platform: dagger.Platform) -> str: - return f"hello {platform}" + def from_platform(self, platform: dagger.Platform) -> str: + return str(platform) + + @function + def to_platform(self, platform: str) -> dagger.Platform: + return dagger.Platform(platform) `, }, } { @@ -2926,12 +2933,91 @@ class Test: c, ctx := connect(t) modGen := modInit(t, c, tc.sdk, tc.source) - out, err := modGen.With(daggerQuery(`{test{sayHello(platform: "linux/amd64")}}`)).Stdout(ctx) + out, err := modGen.With(daggerQuery(`{test{fromPlatform(platform: "linux/amd64")}}`)).Stdout(ctx) require.NoError(t, err) - require.Equal(t, "hello linux/amd64", gjson.Get(out, "test.sayHello").String()) + require.Equal(t, "linux/amd64", gjson.Get(out, "test.fromPlatform").String()) - _, err = modGen.With(daggerQuery(`{test{sayHello(platform: "invalid")}}`)).Sync(ctx) + _, err = modGen.With(daggerQuery(`{test{fromPlatform(platform: "invalid")}}`)).Stdout(ctx) require.ErrorContains(t, err, "unknown operating system or architecture") + + out, err = modGen.With(daggerQuery(`{test{toPlatform(platform: "linux/amd64")}}`)).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "linux/amd64", gjson.Get(out, "test.toPlatform").String()) + + _, err = modGen.With(daggerQuery(`{test{toPlatform(platform: "invalid")}}`)).Sync(ctx) + require.ErrorContains(t, err, "unknown operating system or architecture") + }) + } +} + +func TestModuleEnumType(t *testing.T) { + t.Parallel() + + type testCase struct { + sdk string + source string + } + for _, tc := range []testCase{ + { + sdk: "go", + source: `package main + +type Test struct{} + +func (m *Test) FromProto(proto NetworkProtocol) string { + return string(proto) +} + +func (m *Test) ToProto(proto string) NetworkProtocol { + return NetworkProtocol(proto) +} +`, + }, + { + sdk: "python", + source: `import dagger +from dagger import function, object_type + +@object_type +class Test: + @function + def from_proto(self, proto: dagger.NetworkProtocol) -> str: + return str(proto) + + @function + def to_proto(self, proto: str) -> dagger.NetworkProtocol: + # Doing "dagger.NetworkProtocol(proto)" will fail in Python, so mock + # it to force sending the invalid value back to the server. + from dagger.client.base import Enum + + class MockEnum(Enum): + TCP = "TCP" + INVALID = "INVALID" + + return MockEnum(proto) +`, + }, + } { + tc := tc + + t.Run(tc.sdk, func(t *testing.T) { + t.Parallel() + c, ctx := connect(t) + modGen := modInit(t, c, tc.sdk, tc.source) + + out, err := modGen.With(daggerQuery(`{test{fromProto(proto: "TCP")}}`)).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "TCP", gjson.Get(out, "test.fromProto").String()) + + _, err = modGen.With(daggerQuery(`{test{fromProto(proto: "INVALID")}}`)).Stdout(ctx) + require.ErrorContains(t, err, "invalid enum value") + + out, err = modGen.With(daggerQuery(`{test{toProto(proto: "TCP")}}`)).Stdout(ctx) + require.NoError(t, err) + require.Equal(t, "TCP", gjson.Get(out, "test.toProto").String()) + + _, err = modGen.With(daggerQuery(`{test{toProto(proto: "INVALID")}}`)).Sync(ctx) + require.ErrorContains(t, err, "invalid enum value") }) } } From dfb63a750011303be89c087675cd57c8d84ce8dd Mon Sep 17 00:00:00 2001 From: Helder Correia <174525+helderco@users.noreply.github.com> Date: Thu, 2 May 2024 18:11:08 +0000 Subject: [PATCH 10/15] Fix linter on changed function Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com> --- sdk/python/src/dagger/mod/_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/src/dagger/mod/_converter.py b/sdk/python/src/dagger/mod/_converter.py index b8c7c300435..a072f82c2c2 100644 --- a/sdk/python/src/dagger/mod/_converter.py +++ b/sdk/python/src/dagger/mod/_converter.py @@ -67,7 +67,7 @@ def dagger_type_unstructure(obj): @functools.cache -def to_typedef(annotation: type) -> "TypeDef": # noqa: C901,PLR0912 +def to_typedef(annotation: type) -> "TypeDef": # noqa: C901,PLR0911 """Convert Python object to API type.""" assert not is_annotated( annotation From 31f26e00997bd6f9ef95a095cf43c8931b7652e9 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Fri, 3 May 2024 13:57:19 +0100 Subject: [PATCH 11/15] chore: regen rust Signed-off-by: Justin Chadwell --- sdk/rust/crates/dagger-sdk/src/gen.rs | 34 ++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/sdk/rust/crates/dagger-sdk/src/gen.rs b/sdk/rust/crates/dagger-sdk/src/gen.rs index f0f6a85cda0..78e17d92573 100644 --- a/sdk/rust/crates/dagger-sdk/src/gen.rs +++ b/sdk/rust/crates/dagger-sdk/src/gen.rs @@ -974,14 +974,32 @@ impl PortId { } #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] pub struct ScalarTypeDefId(pub String); -impl Into for &str { - fn into(self) -> ScalarTypeDefId { - ScalarTypeDefId(self.to_string()) +impl From<&str> for ScalarTypeDefId { + fn from(value: &str) -> Self { + Self(value.to_string()) + } +} +impl From for ScalarTypeDefId { + fn from(value: String) -> Self { + Self(value) + } +} +impl IntoID for ScalarTypeDef { + fn into_id( + self, + ) -> std::pin::Pin< + Box> + Send>, + > { + Box::pin(async move { self.id().await }) } } -impl Into for String { - fn into(self) -> ScalarTypeDefId { - ScalarTypeDefId(self.clone()) +impl IntoID for ScalarTypeDefId { + fn into_id( + self, + ) -> std::pin::Pin< + Box> + Send>, + > { + Box::pin(async move { Ok::(self) }) } } impl ScalarTypeDefId { @@ -6254,13 +6272,13 @@ impl Query { } } /// Load a ScalarTypeDef from its ID. - pub fn load_scalar_type_def_from_id(&self, id: ScalarTypeDef) -> ScalarTypeDef { + pub fn load_scalar_type_def_from_id(&self, id: impl IntoID) -> ScalarTypeDef { let mut query = self.selection.select("loadScalarTypeDefFromID"); query = query.arg_lazy( "id", Box::new(move || { let id = id.clone(); - Box::pin(async move { id.id().await.unwrap().quote() }) + Box::pin(async move { id.into_id().await.unwrap().quote() }) }), ); ScalarTypeDef { From 133f37261d9461e61c01736c3ba8d51f686d1f7f Mon Sep 17 00:00:00 2001 From: Tom Chauveau Date: Fri, 3 May 2024 15:37:18 +0200 Subject: [PATCH 12/15] feat: support scalar in TypeScript Signed-off-by: Tom Chauveau --- core/integration/module_test.go | 18 ++++++++++ sdk/typescript/entrypoint/load.ts | 14 ++++++++ sdk/typescript/entrypoint/register.ts | 3 ++ .../scanner/abtractions/argument.ts | 7 ++-- .../scanner/abtractions/method.ts | 7 ++-- .../scanner/abtractions/property.ts | 7 ++-- .../introspector/scanner/typeDefs.ts | 24 +++++++++---- sdk/typescript/introspector/scanner/utils.ts | 25 +++++++++++-- sdk/typescript/introspector/test/scan.spec.ts | 4 +++ .../test/testdata/scalar/expected.json | 35 +++++++++++++++++++ .../test/testdata/scalar/index.ts | 10 ++++++ 11 files changed, 130 insertions(+), 24 deletions(-) create mode 100644 sdk/typescript/introspector/test/testdata/scalar/expected.json create mode 100644 sdk/typescript/introspector/test/testdata/scalar/index.ts diff --git a/core/integration/module_test.go b/core/integration/module_test.go index 1444666cd35..b0b3286efa6 100644 --- a/core/integration/module_test.go +++ b/core/integration/module_test.go @@ -2923,6 +2923,24 @@ class Test: @function def to_platform(self, platform: str) -> dagger.Platform: return dagger.Platform(platform) +`, + }, + { + sdk: "typescript", + source: `import { object, func, Platform } from "@dagger.io/dagger" + +@object() +class Test { + @func() + fromPlatform(platform: Platform): string { + return platform as string + } + + @func() + toPlatform(platform: string): Platform { + return platform as Platform + } +} `, }, } { diff --git a/sdk/typescript/entrypoint/load.ts b/sdk/typescript/entrypoint/load.ts index ccb2d533ab4..aa60372079c 100644 --- a/sdk/typescript/entrypoint/load.ts +++ b/sdk/typescript/entrypoint/load.ts @@ -151,6 +151,20 @@ export async function loadValue( // TODO(supports subfields serialization) return value } + case TypeDefKind.ScalarKind: { + const scalarType = (type as TypeDef).name + + // Workaround to call get any scalar that has an id + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (dag[`load${scalarType}FromID`]) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return dag[`load${scalarType}FromID`](value) + } + + return value + } // Cannot use `,` to specify multiple matching case so instead we use fallthrough. case TypeDefKind.StringKind: case TypeDefKind.IntegerKind: diff --git a/sdk/typescript/entrypoint/register.ts b/sdk/typescript/entrypoint/register.ts index 6b6271381da..a75e349250b 100644 --- a/sdk/typescript/entrypoint/register.ts +++ b/sdk/typescript/entrypoint/register.ts @@ -13,6 +13,7 @@ import { FunctionTypedef, ListTypeDef, ObjectTypeDef, + ScalarTypeDef, TypeDef as ScannerTypeDef, } from "../introspector/scanner/typeDefs.js" @@ -130,6 +131,8 @@ function addArg(args: { */ function addTypeDef(type: ScannerTypeDef): TypeDef { switch (type.kind) { + case TypeDefKind.ScalarKind: + return dag.typeDef().withScalar((type as ScalarTypeDef).name) case TypeDefKind.ObjectKind: return dag.typeDef().withObject((type as ObjectTypeDef).name) case TypeDefKind.ListKind: diff --git a/sdk/typescript/introspector/scanner/abtractions/argument.ts b/sdk/typescript/introspector/scanner/abtractions/argument.ts index f0cf338dd60..6595f26f37d 100644 --- a/sdk/typescript/introspector/scanner/abtractions/argument.ts +++ b/sdk/typescript/introspector/scanner/abtractions/argument.ts @@ -3,8 +3,7 @@ import ts from "typescript" import { UnknownDaggerError } from "../../../common/errors/UnknownDaggerError.js" import { TypeDefKind } from "../../../api/client.gen.js" import { FunctionArgTypeDef, TypeDef } from "../typeDefs.js" -import { serializeType } from "../serialize.js" -import { typeNameToTypedef } from "../utils.js" +import { typeToTypedef } from "../utils.js" export type Arguments = { [name: string]: Argument } @@ -79,9 +78,7 @@ export class Argument { this.symbol.valueDeclaration, ) - const typeName = serializeType(this.checker, type) - - return typeNameToTypedef(typeName) + return typeToTypedef(this.checker, type) } get defaultValue(): string | undefined { diff --git a/sdk/typescript/introspector/scanner/abtractions/method.ts b/sdk/typescript/introspector/scanner/abtractions/method.ts index b00b0aeb673..69bb4a1b1d7 100644 --- a/sdk/typescript/introspector/scanner/abtractions/method.ts +++ b/sdk/typescript/introspector/scanner/abtractions/method.ts @@ -2,8 +2,7 @@ import ts from "typescript" import { UnknownDaggerError } from "../../../common/errors/UnknownDaggerError.js" import { Argument, Arguments } from "./argument.js" -import { serializeType } from "../serialize.js" -import { typeNameToTypedef } from "../utils.js" +import { typeToTypedef } from "../utils.js" import { TypeDefKind } from "../../../api/client.gen.js" import { FunctionArgTypeDef, FunctionTypedef, TypeDef } from "../typeDefs.js" @@ -112,9 +111,7 @@ export class Method { * Return the type of the return value in a Dagger TypeDef format. */ get returnType(): TypeDef { - return typeNameToTypedef( - serializeType(this.checker, this.signature.getReturnType()), - ) + return typeToTypedef(this.checker, this.signature.getReturnType()) } get typeDef(): FunctionTypedef { diff --git a/sdk/typescript/introspector/scanner/abtractions/property.ts b/sdk/typescript/introspector/scanner/abtractions/property.ts index dbc5a32f3fb..89244fb9e02 100644 --- a/sdk/typescript/introspector/scanner/abtractions/property.ts +++ b/sdk/typescript/introspector/scanner/abtractions/property.ts @@ -1,8 +1,7 @@ import ts from "typescript" import { UnknownDaggerError } from "../../../common/errors/UnknownDaggerError.js" -import { serializeType } from "../serialize.js" -import { typeNameToTypedef } from "../utils.js" +import { typeToTypedef } from "../utils.js" import { FieldTypeDef, TypeDef } from "../typeDefs.js" import { TypeDefKind } from "../../../api/client.gen.js" @@ -99,9 +98,7 @@ export class Property { this.symbol.valueDeclaration, ) - const typeName = serializeType(this.checker, type) - - return typeNameToTypedef(typeName) + return typeToTypedef(this.checker, type) } get isExposed(): boolean { diff --git a/sdk/typescript/introspector/scanner/typeDefs.ts b/sdk/typescript/introspector/scanner/typeDefs.ts index 8c449ef351f..e0eaa196849 100644 --- a/sdk/typescript/introspector/scanner/typeDefs.ts +++ b/sdk/typescript/introspector/scanner/typeDefs.ts @@ -15,6 +15,15 @@ export type ObjectTypeDef = BaseTypeDef & { name: string } +/** + * Extends the base typedef if it's a scalar to add its name and real type. + */ +export type ScalarTypeDef = BaseTypeDef & { + kind: TypeDefKind.ScalarKind + typeDef: TypeDef + name: string +} + /** * Extends the base if it's a list to add its subtype. */ @@ -27,15 +36,18 @@ export type ListTypeDef = BaseTypeDef & { * A generic TypeDef that will dynamically add necessary properties * depending on its type. * - * If it's type of kind list, it transforms the BaseTypeDef into an ObjectTypeDef. + * If it's a type of kind scalar, it transforms the BaseTypeDef into a ScalarTypeDef. + * If it's type of kind object, it transforms the BaseTypeDef into an ObjectTypeDef. * If it's a type of kind list, it transforms the BaseTypeDef into a ListTypeDef. */ export type TypeDef = - T extends TypeDefKind.ObjectKind - ? ObjectTypeDef - : T extends TypeDefKind.ListKind - ? ListTypeDef - : BaseTypeDef + T extends TypeDefKind.ScalarKind + ? ScalarTypeDef + : T extends TypeDefKind.ObjectKind + ? ObjectTypeDef + : T extends TypeDefKind.ListKind + ? ListTypeDef + : BaseTypeDef /** * The type of field in a class diff --git a/sdk/typescript/introspector/scanner/utils.ts b/sdk/typescript/introspector/scanner/utils.ts index 97b4428d511..84bd3a093b4 100644 --- a/sdk/typescript/introspector/scanner/utils.ts +++ b/sdk/typescript/introspector/scanner/utils.ts @@ -2,6 +2,7 @@ import ts from "typescript" import { TypeDefKind } from "../../api/client.gen.js" import { TypeDef } from "./typeDefs.js" +import { serializeType } from "./serialize.js" /** * Return true if the given class declaration has the decorator @obj() on @@ -61,15 +62,23 @@ export function isFunction(method: ts.MethodDeclaration): boolean { } /** - * Convert a typename into a Dagger Typedef using dynamic typing. + * Convert a type into a Dagger Typedef using dynamic typing. */ -export function typeNameToTypedef(typeName: string): TypeDef { +export function typeToTypedef( + checker: ts.TypeChecker, + type: ts.Type, + typeName: string = serializeType(checker, type), +): TypeDef { // If it's a list, remove the '[]' and recall the function to get // the type of list if (typeName.endsWith("[]")) { return { kind: TypeDefKind.ListKind, - typeDef: typeNameToTypedef(typeName.slice(0, typeName.length - 2)), + typeDef: typeToTypedef( + checker, + type, + typeName.slice(0, typeName.length - 2), + ), } } @@ -83,6 +92,16 @@ export function typeNameToTypedef(typeName: string): TypeDef { case "void": return { kind: TypeDefKind.VoidKind } default: + // If it's an union, then it's a scalar type + if (type.isUnionOrIntersection()) { + return { + kind: TypeDefKind.ScalarKind, + name: typeName, + typeDef: typeToTypedef(checker, type.types[0]), + } + } + + // Otherwise, it's an object return { kind: TypeDefKind.ObjectKind, name: typeName, diff --git a/sdk/typescript/introspector/test/scan.spec.ts b/sdk/typescript/introspector/test/scan.spec.ts index 882936f8701..ae97dc6a81d 100644 --- a/sdk/typescript/introspector/test/scan.spec.ts +++ b/sdk/typescript/introspector/test/scan.spec.ts @@ -70,6 +70,10 @@ describe("scan static TypeScript", function () { name: "Should correctly scan multiple objects as fields", directory: "multipleObjectsAsFields", }, + { + name: "Should correctly scan scalar arguments", + directory: "scalar", + }, ] for (const test of testCases) { diff --git a/sdk/typescript/introspector/test/testdata/scalar/expected.json b/sdk/typescript/introspector/test/testdata/scalar/expected.json new file mode 100644 index 00000000000..73e61886815 --- /dev/null +++ b/sdk/typescript/introspector/test/testdata/scalar/expected.json @@ -0,0 +1,35 @@ +{ + "name": "Scalar", + "objects": { + "Scalar": { + "name": "Scalar", + "description": "", + "methods": { + "helloWorld": { + "name": "helloWorld", + "description": "", + "arguments": { + "name": { + "name": "name", + "description": "", + "type": { + "kind": "SCALAR_KIND", + "name": "Platform", + "typeDef": { + "kind": "STRING_KIND" + } + }, + "isVariadic": false, + "isNullable": false, + "isOptional": false + } + }, + "returnType": { + "kind": "STRING_KIND" + } + } + }, + "properties": {} + } + } +} \ No newline at end of file diff --git a/sdk/typescript/introspector/test/testdata/scalar/index.ts b/sdk/typescript/introspector/test/testdata/scalar/index.ts new file mode 100644 index 00000000000..53cf073e557 --- /dev/null +++ b/sdk/typescript/introspector/test/testdata/scalar/index.ts @@ -0,0 +1,10 @@ +import { func, object } from '../../../decorators/decorators.js' +import { Platform } from '../../../../api/client.gen.js' + +@object() +export class Scalar { + @func() + helloWorld(name: Platform): string { + return `hello ${name}` + } +} \ No newline at end of file From bd12123459dd0f054953173d1ebf316cc4e78452 Mon Sep 17 00:00:00 2001 From: Tom Chauveau Date: Fri, 3 May 2024 15:46:45 +0200 Subject: [PATCH 13/15] feat: add enum tests Signed-off-by: Tom Chauveau --- core/integration/module_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/integration/module_test.go b/core/integration/module_test.go index b0b3286efa6..ec1e3ab3af8 100644 --- a/core/integration/module_test.go +++ b/core/integration/module_test.go @@ -3013,6 +3013,24 @@ class Test: INVALID = "INVALID" return MockEnum(proto) +`, + }, + { + sdk: "typescript", + source: `import { object, func, NetworkProtocol } from "@dagger.io/dagger"; + +@object() +class Test { + @func() + fromProto(Proto: NetworkProtocol): string { + return Proto as string; + } + + @func() + toProto(Proto: string): NetworkProtocol { + return Proto as NetworkProtocol; + } +} `, }, } { From a01f0b4029dcf2570c802c80f2149425513b4d22 Mon Sep 17 00:00:00 2001 From: Helder Correia <174525+helderco@users.noreply.github.com> Date: Fri, 3 May 2024 16:09:19 +0000 Subject: [PATCH 14/15] Remove old tests Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com> --- core/integration/module_python_test.go | 42 -------------------------- 1 file changed, 42 deletions(-) diff --git a/core/integration/module_python_test.go b/core/integration/module_python_test.go index 699cf6210f9..21f26c96370 100644 --- a/core/integration/module_python_test.go +++ b/core/integration/module_python_test.go @@ -1420,48 +1420,6 @@ func TestModulePythonWithOtherModuleTypes(t *testing.T) { }) } -func TestModulePythonScalarKind(t *testing.T) { - t.Parallel() - - c, ctx := connect(t) - - _, err := pythonModInit(t, c, ` - import dagger - from dagger import dag, function, object_type - - @object_type - class Test: - @function - def foo(self, platform: dagger.Platform) -> dagger.Container: - return dag.container(platform=platform) - `). - With(daggerCall("foo", "--platform", "linux/arm64")). - Sync(ctx) - - require.ErrorContains(t, err, "not supported yet") -} - -func TestModulePythonEnumKind(t *testing.T) { - t.Parallel() - - c, ctx := connect(t) - - _, err := pythonModInit(t, c, ` - import dagger - from dagger import dag, function, object_type - - @object_type - class Test: - @function - def foo(self, protocol: dagger.NetworkProtocol) -> dagger.Container: - return dag.container().with_exposed_port(8000, protocol=protocol) - `). - With(daggerCall("foo", "--protocol", "UDP")). - Sync(ctx) - - require.ErrorContains(t, err, "not supported yet") -} - func pythonSource(contents string) dagger.WithContainerFunc { return pythonSourceAt("", contents) } From 080503189dbced802a62b56ca26d2f8494633f37 Mon Sep 17 00:00:00 2001 From: Helder Correia <174525+helderco@users.noreply.github.com> Date: Mon, 6 May 2024 12:41:46 +0000 Subject: [PATCH 15/15] Add change log Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com> --- .changes/unreleased/Added-20240506-124137.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/Added-20240506-124137.yaml diff --git a/.changes/unreleased/Added-20240506-124137.yaml b/.changes/unreleased/Added-20240506-124137.yaml new file mode 100644 index 00000000000..ecaad866be0 --- /dev/null +++ b/.changes/unreleased/Added-20240506-124137.yaml @@ -0,0 +1,6 @@ +kind: Added +body: Added support for custom scalars and enums in function arguments +time: 2024-05-06T12:41:37.849472Z +custom: + Author: jedevc + PR: "7158"