Open dmjio opened 1 year ago
Hello! No, theres been no explorartion of any sort like that. What would the API look like? If you're just talking about a conversion Aeson.Value -> Maybe (Object schema)
, you could just do Aeson.parseMaybe parseJSON
. We could certainly make a helper for that, but otherwise, I'm not sure what a smart constructor would entail
Thanks for the quick response.
I'm not really sure what a builder for a schema-abiding Value
would look like either. But one idea could be something like ...
Given this schema:
type PersonSchema = [schema|
{
person: {
name: Name,
age: Age
}
}
|]
We could define some paths we want to set w/ values.
type PathSchema = '[ '("person.name", Name), '("person.age", Age) ]
schemaBuilderPerson :: Proxy PathSchema
schemaBuilderPerson = Proxy
In order to ensure we could encode
a Schema
statically, we'd need some type family to check the paths against the type level Person
Schema
defined above. The type family should also check if some paths haven't been specified as well (reported as errors). If valid, an empty anonymous record / HList
/ or empty aeson
Object
could be returned.
validate
:: (ValidSchema pathSchema schema)
=> Proxy pathSchema
-> Proxy schema
-> ValidSchemaBuilder pathSchema schema
validate = ValidSchemaBuilder mempty
Once the valid
function returns a ValidSchemaBuilder schema builder
, the builder
portion of the ValidSchemaBuilder schema builder
can be deconstructed. set
would have the effect of peeling off a type in the type level list, but also populating the anonymous record / HList / object.
setPersonSchema
:: Name
-> Age
-> ValidSchemaBuilder PersonSchema PathSchema
-> ValidSchemaBuilder PersonSchema '[]
-- ^ the act of calling `set` removes a path in the type-level path list of `ValidSchemaBuilder`.
setPersonSchema name age (ValidSchemaBuilder schema)
= ValidSchemaBuilder $ schema
& set (Proxy @ "person.name") name
& set (Proxy @ "person.age") age
And then finally we can encode constructed ValidSchemaBuilder schema '[]
into Value
.
validSchemaBuilderToJSON :: ValidSchemaBuilder schema '[] -> Value
-- ^ builder ~ '[] in order to make `Value`
I haven't really fully fleshed out this idea, but I think the above is possible.
:sparkles: This is an old work account. Please reference @brandonchinn178 for all future communication :sparkles:
Interesting! Yes, that certainly looks possible, but I'm not sure we need that many new concepts. It should be possible to do something like
type PersonSchema = [schema| { person: { name: Name, age: Age } } |]
-- buildObject :: [SomeField] -> Maybe (Object PersonSchema)
buildObject
[ field @'["person", "name"] "Alice"
, field @'["person", "age"] 20
]
data SomeField = forall path a. SomeField (Field path a)
data Field path a = Field
{ path :: [String] -- value-representation of type level 'path'
, value :: a
}
field :: forall path a. All KnownSymbol path => a -> Field path a
I'd certainly be open to a PR of that sort
Note: somewhat related to #2.
@brandonchinn178 In your example, is the "person" type save?
If I made a typo
buildObject
[ field @'["person2", "name"] "Alice"
, field @'["person", "age"] 20
]
Would it error?
No, it wouldn't error in my example, it would return Nothing at runtime, as the object it builds up would have a person key and a person2 key, which doesnt match the PersonSchema schema.
Rereading the thread, I guess my example didn't really answer the original question. In general, this problem could be solved in two ways:
Solution (2) / OP's example could be implemented, but I don't see how the example would prevent someone from specifying a PathsSchema that doesnt match PersonSchema.
So I think (1) would be better. It could be done with something like
data PartialObject paths = UnsafePartialObject [([String], Dynamic)]
set ::
forall path a paths.
(ToJSON a, AllKnownSymbol path) =>
a
-> PartialObject paths
-> PartialObject (path ': paths)
set a (UnsafePartialObject paths) =
UnsafePartialObject $ (getPath @path, toDyn a) : paths
class AllKnownSymbol path where
getPath :: [String]
instance AllKnownSymbol [] where
getPath = []
instance
( KnownSymbol s
, AllKnownSymbol ss
) => AllKnownSymbol (s ': ss) where
getPath = knownSym s : getPath @ss
validate ::
forall schema paths.
Matches schema paths =>
PartialObject paths
-> Object schema
validate (UnsafePartialObject paths) = UnsafeObject (foldr insertPath Map.empty paths)
where
insertPath :: ([String], Dynamic) -> Map Text Dynamic -> Map Text Dynamic
insertPath = _
The hard part here is defining the type family Matches schema paths
. It might get fairly hairy, but I'm not sure.
Hello 👋🏼
Great work on this library.
I was curious / had a potential feature request. Has there been any exploration into generating smart constructors / setters that would allow a schema-abiding
Value
to be constructed? Would be nice if it could also guarantee all fields / leaves had been populated.Thanks again for this library.