bundleofbytes / libstripe

Rust implementation of Stripe API
Apache License 2.0
5 stars 2 forks source link

Code generation using Stripe OpenAPI #5

Open dariusc93 opened 5 years ago

dariusc93 commented 5 years ago

The initial idea I had was to build a tool to parse a script and perform code generation for the API, but since I am now noticing that stripe have a open spec of their api, it may be best to parse and use that instead. Using spec3 from https://github.com/stripe/openapi (which would be more up to date), we could possibly perform some code generation to keep the API up to date.

dariusc93 commented 5 years ago

More of an internal note:

OpenAPI, while it gives alot of insight on different things regarding the stripe api, it also open up abit more questions.

  1. Should there be two passes through the spec to gather information before generation of the code so there could be information at hand to work from? Could always load everything at runtime just once, but I do see instances where I would like to have information to work off of first before generating than to generate right out. Eg, listing all references, objects, etc. May not be necessary but having information doesnt hurt either.
  2. Should the components schemas reference be stored locally and map to be store in specific modules? Eg, linking #/components/schemas/card to src/resources/paymentmethods/cards.rs, etc.
  3. What would be the best way to handle anything introduced to the spec? Since stripe havent marked anything within it as "preview" or "beta", this do bring some questions on handling new api that may just be a preview.

If I wish to maintain current format and reduce possible breaking change, manual intervention might be needed in some areas (eg noting anything that is "preview" or "beta" or maybe even go as far as having it under a feature?). While we want to be able to generate the code, we also want to maintain stability as much as possible. May be best to implement test to make sure everything will continue to work as expected.

dariusc93 commented 5 years ago

While rewriting what I have, I decided that it would be best to stick with the current layout to make sure things are kept organized and to reduce breaking changes. In the process, I decided to have a mapping file that would be used to map specific objects to file within a resource directory. Eg mapping coupon to "src/resource/billing", etc. Currently, I am thinking of having it in this layout to define the structs that goes in which file under that directory

{
    "billing": {
        "path": "src/resources/billing",
        "resource": [
            {
                "coupons": {
                    "objects": [
                        {
                            "Coupon":{
                                "link_fields": [{
                                    "currency": "Currency",
                                    "duration": "Duration"
                                }]
                            }
                        },
                        {
                            "Duration": null
                        }, 
                        {
                            "CouponParam":{
                                "link_fields": [{
                                    "currency": "Currency",
                                    "duration": "Duration"
                                }]
                            }
                        },
                        {
                            "CouponListParam":{
                                "link_fields": [{
                                    "created": "RangeQuery"
                                }]
                            }
                        }
                    ]
                },
                "credit_notes": {
                    "objects": [
                        {
                            "CreditNotes":{
                                "link_fields": [{
                                    "currency": "Currency",
                                    "customer": "Customer",
                                    "invoice": "Invoice",
                                    "reason": "CreditNoteReason",
                                    "refund": "Refund",
                                    "type": "CreditNoteType"
                                }]
                            }
                        },
                        {
                            "CreditNoteReason": null
                        },
                        {
                            "CreditNoteStatus": null
                        },
                        {
                            "CreditNoteType": null
                        }, 
                        {
                            "CreditNoteParam":{
                                "link_fields": [{
                                    "reason": "CreditNoteReason"
                                }]
                            }
                        },
                        {
                            "CreditNoteListParam":{
                                "link_fields": null
                            }
                        }
                    ]
                }
            }
        ]
    },
    "checkout": null,
    "common": null,
    "connect": null,
    "core": null,
    "fraud": null,
    "issuing": null,
    "orders": null,
    "paymentmethods": null,
    "sigma": null,
    "terminal": null,
    "webhooks": null
}

Of course, this is subject to change as I may decide to write a script to run through stripe OpenAPI spec and map things accordingly (including having the ref contained in the spec to be mapped to the proper object) so when it come time to generating the code, it will output according to the format in the mapping and any new objects not here will likely go into a misc directory.

This still havent really resolved handling any preview or beta api calls, which may have to be marked manually and to be activated with a feature (due to the possibility of a breaking change since anything in preview is likely to change more often than anything outside of it).

Additional fields in the mapping file may be used to act as a way of renaming, however, that idea may be scratched out before publishing any code since some common things such as "type" would contain a prefix of what the type is used for. Eg, in CreditNote, "type" would be renamed to "credit_note_type", etc. Any custom enums we have (eg CardBrand), we may have the mapping file reference an enum since in the spec the brand doesnt reference one. Expandable fields do not need to be included in the file since the spec do contains which fields are expandable. Same with any objects or fields that are optional (or "nullable").

Ideally, once I get the time to finish the rewrite of the tool, the mapping file should be the only thing needing to be modified since it will be used as a reference.