Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] Adds a "dynamic" module to dynamically load services from smithy models #17

Merged
merged 41 commits into from
Feb 14, 2022

Conversation

Baccata
Copy link
Contributor

@Baccata Baccata commented Dec 27, 2021

This relies on a one-pass + suspension to build a map of schemas that work against dynamic data structures, backing product types with Array[Any], and union-types with (Int, Any)

The goal is notably to get to a state where services could be proxied dynamically, whether for actual proxyfication purposes, or to create CLIs that do not need recompilation, or create UIs/playgrounds dynamically.

This relies on mutability + suspension to build a map of schemas that
work against dynamic data, backing product types with arrays.

The goal is notably to get to a state where services could be proxied
dynamically, whether for actual proxyfication purposes, or to create
CLIs that do not need recompilation, or create UIs/playgrounds
dynamically.
build.sbt Outdated Show resolved Hide resolved
@Baccata
Copy link
Contributor Author

Baccata commented Dec 28, 2021

Don't trust the CI, the new module is not currently wired (nor do I intend to do it right away)

@Baccata Baccata changed the title [POC] Adds a "dynamic" module to dynamically load services from smithy modules [POC] Adds a "dynamic" module to dynamically load services from smithy models Dec 28, 2021
@kubukoz
Copy link
Member

kubukoz commented Jan 4, 2022

I can now confirm this works on JS :)

@kubukoz
Copy link
Member

kubukoz commented Jan 15, 2022

@Baccata, eeef6b8 (#17) adds a failing test for something I've experienced when trying to compile dynamic schemas in the other CLI module. The stack trace in this test is as follows:

[E]   NoSuchElementException: key not found: smithy.api#String
[E] 
[E]   Map.scala:274                           scala.collection.MapOps#default
[E]   Map.scala:273                           scala.collection.MapOps#default$
[E]   Map.scala:405                           scala.collection.AbstractMap#default
[E]   HashMap.scala:425                       scala.collection.mutable.HashMap#apply
[E]   DynamicModelCompiler.scala:82           smithy4s.dynamic.Compiler$CompileVisitor#schema
[E]   DynamicModelCompiler.scala:180          smithy4s.dynamic.Compiler$CompileVisitor#$anonfun$structureShape$3
[E]   suspended.scala:32                      schematic.suspended$Schema#deferredSchema$lzycompute
[E]   suspended.scala:32                      schematic.suspended$Schema#deferredSchema
[E]   suspended.scala:35                      schematic.suspended$Schema#$anonfun$compile$1
[E]   DynamicModelSpec.scala:131              smithy4s.dynamic.model.DynamicModelSpec$Interpreter$GetFieldNames$#$anonfun$suspend$1
[E]   DynamicModelSpec.scala:127              smithy4s.dynamic.model.DynamicModelSpec$Interpreter$GetFieldNames$#$anonfun$genericStruct$2
[E]   StrictOptimizedIterableOps.scala:118    scala.collection.StrictOptimizedIterableOps#flatMap
[E]   StrictOptimizedIterableOps.scala:105    scala.collection.StrictOptimizedIterableOps#flatMap$
[E]   Vector.scala:113                        scala.collection.immutable.Vector#flatMap
[E]   DynamicModelSpec.scala:126              smithy4s.dynamic.model.DynamicModelSpec$Interpreter$GetFieldNames$#$anonfun$genericStruct$1
[E]   DynamicModelSpec.scala:131              smithy4s.dynamic.model.DynamicModelSpec$Interpreter$GetFieldNames$#$anonfun$suspend$1
[E]   DynamicModelSpec.scala:163              smithy4s.dynamic.model.DynamicModelSpec$Interpreter$#$anonfun$toFieldNames$1
[E]   List.scala:293                          scala.collection.immutable.List#flatMap
[E]   DynamicModelSpec.scala:162              smithy4s.dynamic.model.DynamicModelSpec$Interpreter$#toFieldNames
[E]   DynamicModelSpec.scala:174              smithy4s.dynamic.model.DynamicModelSpec$#$anonfun$new$8
[E]   suites.scala:82                         delay @ weaver.MutableFSuite#$anonfun$pureTest$1
// only weaver stuff below

pureTest(
"Extract field names from all structures in a service's endpoints"
) {
val svc = dynamic.Compiler.compile(expected).allServices.head
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using expected as a base as it's already been verified as the correct output of model dump in the test above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test fails because the smithy prelude is not dumped in the json representation. We would prepopulate the visitor by running it on a handcrafted model containing just the primitives

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. I'll let you know if I get to working on it first :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that, turns out the floatShape visitor was not doing anything in particular - still the case for other cases. E.g. when I tried to compile PizzaSpec's model, it's doesn't recognize Food (a union). I can look at this again after the weekend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that, turns out the floatShape visitor was not doing anything in particular - still the case for other cases. E.g. when I tried to compile PizzaSpec's model, it's doesn't recognize Food (a union). I can look at this again after the weekend.

Yeah, I had left a fair amount there as TODOs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of it should be implemented in eb05278

@kubukoz
Copy link
Member

kubukoz commented Jan 15, 2022

also, can we include this in the root project? It makes local publishing much easier.

@Baccata
Copy link
Contributor Author

Baccata commented Jan 15, 2022

also, can we include this in the root project? It makes local publishing much easier.

@kubukoz sure yeah, go for it :)

@kubukoz
Copy link
Member

kubukoz commented Jan 16, 2022

Added new spec, trying to dump the model of PizzaSpec to find more issues - currently it's failing because it doesn't recognize the @simpleRestJson trait, so we probably need to change how the model assembler is used in that spec. Then there's the union issue: #17 (comment) - some other cases are also probably not being visited

*
* Created for testing purposes.
*/
object JsonIOProtocol {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kubukoz, I've created this to facilitate testing. Given a service given by the dynamic model, we can create a "proxy" function, that round trips a Document => IO[Document] kleisli through the fromJsonIO and toJsonIO functions.

Then, if we take that proxy and create a corresponding statically generated stub from it, in theory the data should go through the proxy back and forth, in an untampered fashion.

We can add some middleware to intercept and store the incoming and outgoing json in a CE-Ref, and either perform assertions on the json itself, or use it for logging purposes when we see that the data has not gone through the dynamic proxy as expected.

import schematic.Field
import schematic.Alt

object DefaultSchematic
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems useful for working with schemas, maybe it could be exposed to the users in some module closer to core?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about, but decided against it because of a lack for a better motivation, at this time. When the time arise, we'll move it

@kubukoz
Copy link
Member

kubukoz commented Jan 26, 2022

Sooo this is currently working in all capacity that I'd need it to, is there anything major that needs to happen before we merge?

@Baccata
Copy link
Contributor Author

Baccata commented Feb 3, 2022

@kubukoz sorry I had missed that. I need to do another pass, clean some things up (make most of them private) and move stuff to an internalspackage. I'll try to do it next week.

Move most function to the internals package, make most things private
@Baccata Baccata marked this pull request as ready for review February 9, 2022 10:32
@Baccata
Copy link
Contributor Author

Baccata commented Feb 9, 2022

@kubukoz I've cleaned things up, renamed some things, etc. Time for some bikeshedding :)

@Baccata Baccata merged commit 5eed69a into main Feb 14, 2022
@Baccata Baccata deleted the dynamic-schemas branch February 14, 2022 09:41
Baccata added a commit that referenced this pull request May 10, 2022
… smithy models (#17)

Adds a dynamic module to dynamically load existential `Service` and `Schema` instances from the json-representation of smithy models. The construct exposed for doing this is called `DynamicSchemaIndex` 

Because the `Service` and `Schema` interface do not make any constraint with regards to how datatypes are stored in memory, it is possible to reify Schemas that work against generic representations: 

* products are backed by Array[Any] 
* sums are backed by (Int, Any) tuples 

Because of this, we can load `Service` instances in an existential fashion, which allows to connect front-ends (service stubs) and back-ends (client stubs) from separate protocols without needing any code generation. 

An example use-case for it is the derivation of fully dynamic CLIs that read a spec file to expose the commands, and invoke http apis via the compiled clients. 

Another use-case could be dynamic http mocks that can be hot-reloaded. 

Co-authored-by: Olivier Mélois <baccata64@gmail.com>
Co-authored-by: Jakub Kozłowski <kubukoz@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants