Repository: strawberry-graphql/strawberry

Issue:
Parity with `dataclasses.field`
While starting to use Strawberry 🍓  and admittedly being a beginner, I noticed I had to make a choice : I have to choose between Strawberry features on my fields via `strawberry.field` (e.g. adding a description) and using native `dataclasses.field` features.

Some of the native `dataclasses.field` attributes that I do use on my dataclasses are `default_factory` and `metadata` (which allows me to pass an arbitrary dictionary of extra settings to my field)

Is there a reason why `strawberry.field` doesn't directly extend `dataclasses.field` ? Wouldn't that be more pythonic ? 😺 

Practically speaking:
```python
import faker

# https://faker.readthedocs.io/en/master/
fake = faker.Faker()

@strawberry.type
class User:
    first_name: str = strawberry.field(description="First name",  default_factory=fake.first_name, metadata={"foo": "bar"})
```

This isn't currently possible because `@strawberry.field` doesn't accept `default_factory` and `metadata`.
Would've been cool though, especially in the context of being able to generate fake data on the fly


Discussion:
FYI [pydantic does have](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation) parity with `dataclasses.field`, mostly by allowing `**` extra kwargs in their field function

They consider `default_factory` to be a [beta](https://pydantic-docs.helpmanual.io/usage/models/#field-with-dynamic-default-value) feature tho
I personally want to add these feature, there's a small discussion on adding support for init -> https://github.com/strawberry-graphql/strawberry/issues/539

We'll also need `default_factory` too (this will affect the schema generation too as we expose default values on the schema).

Not sure about metadata, I guess it won't hurt to add it, but I wonder if users would get confused by it? 
What do use it for?
I personally use it to define my data generator, for which I opened a different discussion [here](https://github.com/strawberry-graphql/strawberry/discussions/570) as I believe it is its own topic :)

Looks something like this:
```python
from __future__ import annotations

from abc import ABC
from dataclasses import field, fields

import faker
import strawberry

fake = faker.Faker()

FACTORY_FIELD = "factory"


class FactoryMixin(ABC):
    """
    Abstract class that defines the ability to generate fake data from a dataclass'
    metadata
    """

    @classmethod
    def fake(cls) -> FactoryMixin:
        # Find all fields that define a data generator
        factory_fields = [
            field for field in fields(cls) if field.metadata.get(FACTORY_FIELD)
        ]
        fake_data = {
            field.name: field.metadata[FACTORY_FIELD]() for field in factory_fields
        }
        return cls(**fake_data)


@strawberry.type
class User(FactoryMixin):

    first_name: str = field(metadata={FACTORY_FIELD: fake.first_name})
    last_name: str = field(metadata={FACTORY_FIELD: fake.last_name})
    email: date = field(metadata={FACTORY_FIELD: fake.email})
    phone: str = field(metadata={FACTORY_FIELD: fake.phone_number})

    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}"
```

The `metadata` field, although a bit inexpressive, was literally intended for that usage by the Python core team:

> It is not used at all by Data Classes, and is provided as a third-party extension mechanism. Multiple third-parties can each have their own key, to use as a namespace in the metadata.

In theory, I guess Strawberry should claim it's own namespace in there:
```python
first_name: str = field(metadata={"strawberry": {"description": "Given name"}})
```

Practically speaking, this wouldn't work as you'd lose all the typing information and IDE support that functions and classes provide, which is why Pydantic, Strawberry... no one really uses `metadata`

It did work for me as a drop-in for a possibly new feature though (data generation, that is) 😸  

Resolving patch:
diff --git a/strawberry/exceptions.py b/strawberry/exceptions.py
--- a/strawberry/exceptions.py
+++ b/strawberry/exceptions.py
@@ -143,3 +143,23 @@ def __init__(self, expected: int, received: int):
         )
 
         super().__init__(message)
+
+
+class FieldWithResolverAndDefaultValueError(Exception):
+    def __init__(self, field_name: str, type_name: str):
+        message = (
+            f'Field "{field_name}" on type "{type_name}" cannot define a default '
+            "value and a resolver."
+        )
+
+        super().__init__(message)
+
+
+class FieldWithResolverAndDefaultFactoryError(Exception):
+    def __init__(self, field_name: str, type_name: str):
+        message = (
+            f'Field "{field_name}" on type "{type_name}" cannot define a '
+            "default_factory and a resolver."
+        )
+
+        super().__init__(message)
diff --git a/strawberry/field.py b/strawberry/field.py
--- a/strawberry/field.py
+++ b/strawberry/field.py
@@ -6,14 +6,14 @@
 
 from graphql import GraphQLResolveInfo
 
-from strawberry.arguments import convert_arguments
+from strawberry.arguments import UNSET, convert_arguments
 from strawberry.types.info import Info
 from strawberry.utils.typing import get_parameters, has_type_var, is_type_var
 
 from .arguments import StrawberryArgument
 from .permission import BasePermission
 from .types.fields.resolver import StrawberryResolver
-from .types.types import FederationFieldParams, undefined
+from .types.types import FederationFieldParams
 from .union import StrawberryUnion
 from .utils.str_converters import to_camel_case
 
@@ -38,7 +38,8 @@ def __init__(
         description: Optional[str] = None,
         base_resolver: Optional[StrawberryResolver] = None,
         permission_classes: List[Type[BasePermission]] = (),  # type: ignore
-        default_value: Any = undefined,
+        default_value: Any = UNSET,
+        default_factory: Union[Callable, object] = UNSET,
         deprecation_reason: Optional[str] = None,
     ):
         federation = federation or FederationFieldParams()
@@ -47,8 +48,10 @@ def __init__(
         is_basic_field = not base_resolver
 
         super().__init__(  # type: ignore
-            default=dataclasses.MISSING,
-            default_factory=dataclasses.MISSING,
+            default=(default_value if default_value != UNSET else dataclasses.MISSING),
+            default_factory=(
+                default_factory if default_factory != UNSET else dataclasses.MISSING
+            ),
             init=is_basic_field,
             repr=is_basic_field,
             compare=is_basic_field,
@@ -281,6 +284,8 @@ def field(
     permission_classes: Optional[List[Type[BasePermission]]] = None,
     federation: Optional[FederationFieldParams] = None,
     deprecation_reason: Optional[str] = None,
+    default: Any = UNSET,
+    default_factory: Union[Callable, object] = UNSET,
 ) -> StrawberryField:
     """Annotates a method or property as a GraphQL field.
 
@@ -306,6 +311,8 @@ def field(
         permission_classes=permission_classes or [],
         federation=federation or FederationFieldParams(),
         deprecation_reason=deprecation_reason,
+        default_value=default,
+        default_factory=default_factory,
     )
 
     if resolver:
diff --git a/strawberry/types/type_resolver.py b/strawberry/types/type_resolver.py
--- a/strawberry/types/type_resolver.py
+++ b/strawberry/types/type_resolver.py
@@ -4,6 +4,8 @@
 from typing import Dict, List, Type, cast
 
 from strawberry.exceptions import (
+    FieldWithResolverAndDefaultFactoryError,
+    FieldWithResolverAndDefaultValueError,
     MissingTypesForGenericError,
     PrivateStrawberryFieldError,
 )
@@ -25,12 +27,9 @@
     is_union,
 )
 
-from ..arguments import StrawberryArgument
+from ..arguments import UNSET, StrawberryArgument
 from .generics import copy_type_with, get_name_from_types
 
-# TODO: why do we have undefined?
-from .types import undefined
-
 
 def _resolve_generic_type(type: Type, field_name: str) -> Type:
     if hasattr(type, "_type_definition") and type._type_definition.is_generic:
@@ -381,6 +380,23 @@ class if one is not set by either using an explicit strawberry.field(name=...) o
             if isinstance(field.type, Private):
                 raise PrivateStrawberryFieldError(field.python_name, cls.__name__)
 
+            # Check that default is not set if a resolver is defined
+            if field.default != dataclasses.MISSING and field.base_resolver is not None:
+                raise FieldWithResolverAndDefaultValueError(
+                    field.python_name, cls.__name__
+                )
+
+            # Check that default_factory is not set if a resolver is defined
+            # Note: using getattr because of this issue:
+            # https://github.com/python/mypy/issues/6910
+            if (
+                getattr(field, "default_factory") != dataclasses.MISSING  # noqa
+                and field.base_resolver is not None
+            ):
+                raise FieldWithResolverAndDefaultFactoryError(
+                    field.python_name, cls.__name__
+                )
+
             # we make sure that the origin is either the field's resolver when
             # called as:
             #
@@ -406,7 +422,7 @@ class if one is not set by either using an explicit strawberry.field(name=...) o
                 graphql_name=to_camel_case(field.name),
                 type_=field_type,
                 origin=cls,
-                default_value=getattr(cls, field.name, undefined),
+                default_value=getattr(cls, field.name, UNSET),
             )
 
         field_name = field.graphql_name


Test patch:
diff --git a/tests/schema/test_basic.py b/tests/schema/test_basic.py
--- a/tests/schema/test_basic.py
+++ b/tests/schema/test_basic.py
@@ -7,6 +7,10 @@
 import pytest
 
 import strawberry
+from strawberry.exceptions import (
+    FieldWithResolverAndDefaultFactoryError,
+    FieldWithResolverAndDefaultValueError,
+)
 
 
 def test_raises_exception_with_unsupported_types():
@@ -443,3 +447,49 @@ class Query:
     assert "<strawberry.schema.schema.Schema object" in repr(
         schema
     ), "Repr should not be affected"
+
+
+def test_field_with_default():
+    @strawberry.type
+    class Query:
+        a: str = strawberry.field(default="Example")
+
+    schema = strawberry.Schema(query=Query)
+
+    query = "{ a }"
+
+    result = schema.execute_sync(query, root_value=Query())
+
+    assert not result.errors
+    assert result.data == {"a": "Example"}
+
+
+def test_field_with_resolver_default():
+    with pytest.raises(FieldWithResolverAndDefaultValueError):
+
+        @strawberry.type
+        class Query:
+            @strawberry.field(default="Example C")
+            def c(self) -> str:
+                return "I'm a resolver"
+
+
+def test_field_with_separate_resolver_default():
+    with pytest.raises(FieldWithResolverAndDefaultValueError):
+
+        def test_resolver() -> str:
+            return "I'm a resolver"
+
+        @strawberry.type
+        class Query:
+            c: str = strawberry.field(default="Example C", resolver=test_resolver)
+
+
+def test_field_with_resolver_default_factory():
+    with pytest.raises(FieldWithResolverAndDefaultFactoryError):
+
+        @strawberry.type
+        class Query:
+            @strawberry.field(default_factory=lambda: "Example C")
+            def c(self) -> str:
+                return "I'm a resolver"
diff --git a/tests/types/test_basic.py b/tests/types/test_basic.py
--- a/tests/types/test_basic.py
+++ b/tests/types/test_basic.py
@@ -77,3 +77,21 @@ class Query:
     definition = Query._type_definition
 
     assert definition.fields[0].graphql_name == "some_name"
+
+
+def test_field_with_default():
+    @strawberry.type
+    class Query:
+        the_field: int = strawberry.field(default=3)
+
+    instance = Query()
+    assert instance.the_field == 3
+
+
+def test_field_with_default_factory():
+    @strawberry.type
+    class Query:
+        the_field: int = strawberry.field(default_factory=lambda: 3)
+
+    instance = Query()
+    assert instance.the_field == 3