This is a work in progress!
Parse the metadata XML describing an SAP OData service and generate Rust entities for each EDM type:
ComplexType
EntityType
FunctionImport
TODO
Currently when generating a Rust
struct
, only theName
andType
properties are used. Consider how the other XML attribute values and SAP annotations could be made available within the Ruststruct
.
Currently, the metadata XML for an OData service must be written to a local file that this code generator then reads.
TODO
Consider fetching the metadata at build time — but this raises questions about whether allowing a build script to look outside its sandbox is an anti-pattern.
Yeah, it might well be...
Within the metadata for the GWSAMPLE_BASIC
OData service, there is the following entity type declaration for SalesOrderLineItem
:
xml
<EntityType Name="SalesOrderLineItem" sap:content-version="1">
<Key>
<PropertyRef Name="SalesOrderID"/>
<PropertyRef Name="ItemPosition"/>
</Key>
<Property Name="SalesOrderID" Type="Edm.String" Nullable="false" MaxLength="10" sap:unicode="false" sap:label="Sa. Ord. ID" sap:updatable="false"/>
<Property Name="ItemPosition" Type="Edm.String" Nullable="false" MaxLength="10" sap:unicode="false" sap:label="PO Item Pos" sap:creatable="false" sap:updatable="false"/>
<Property Name="ProductID" Type="Edm.String" Nullable="false" MaxLength="10" sap:unicode="false" sap:label="Product ID"/>
<Property Name="Note" Type="Edm.String" MaxLength="255" sap:unicode="false" sap:label="Description" sap:sortable="false" sap:filterable="false"/>
<Property Name="NoteLanguage" Type="Edm.String" MaxLength="2" sap:unicode="false" sap:label="Language" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
<Property Name="CurrencyCode" Type="Edm.String" MaxLength="5" sap:unicode="false" sap:label="Currency" sap:creatable="false" sap:updatable="false" sap:semantics="currency-code"/>
<Property Name="GrossAmount" Type="Edm.Decimal" Precision="16" Scale="3" sap:unicode="false" sap:unit="CurrencyCode" sap:label="Gross Amt." sap:creatable="false" sap:updatable="false"/>
<Property Name="NetAmount" Type="Edm.Decimal" Precision="16" Scale="3" sap:unicode="false" sap:unit="CurrencyCode" sap:label="Net Amt." sap:creatable="false" sap:updatable="false"/>
<Property Name="TaxAmount" Type="Edm.Decimal" Precision="16" Scale="3" sap:unicode="false" sap:unit="CurrencyCode" sap:label="Tax Amt." sap:creatable="false" sap:updatable="false"/>
<Property Name="DeliveryDate" Type="Edm.DateTime" Nullable="false" Precision="7" sap:unicode="false" sap:label="Time Stamp" sap:sortable="false" sap:filterable="false"/>
<Property Name="Quantity" Type="Edm.Decimal" Nullable="false" Precision="13" Scale="3" sap:unicode="false" sap:unit="QuantityUnit" sap:label="Quantity" sap:sortable="false" sap:filterable="false"/>
<Property Name="QuantityUnit" Type="Edm.String" MaxLength="3" sap:unicode="false" sap:label="Qty. Unit" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" sap:semantics="unit-of-measure"/>
<NavigationProperty Name="ToHeader" Relationship="GWSAMPLE_BASIC.Assoc_SalesOrder_SalesOrderLineItems" FromRole="ToRole_Assoc_SalesOrder_SalesOrderLineItems" ToRole="FromRole_Assoc_SalesOrder_SalesOrderLineItems"/>
<NavigationProperty Name="ToProduct" Relationship="GWSAMPLE_BASIC.Assoc_Product_SalesOrderLineItems" FromRole="ToRole_Assoc_Product_SalesOrderLineItems" ToRole="FromRole_Assoc_Product_SalesOrderLineItems"/>
</EntityType>
From the above definition, the Name
and Type
attributes are used to generate the following basic Rust struct
:
```rust
pub struct SalesOrderLineItem {
pub currencycode: Option
In the event an Entity Type definition uses a complex type, then the complex type is first created as a Rust struct
.
The field in Rust struct
that has this complex type is then defined using this struct
.
An example of this is the Address
property.
```xml
```
The Rust struct
name is generated by trimming the namespace qualifier and (if present) the CT_
prefix
xml
<ComplexType Name="CT_Address">
<Property Name="City" Type="Edm.String" MaxLength="40" sap:label="City" sap:semantics="city"/>
<Property Name="PostalCode" Type="Edm.String" MaxLength="10" sap:label="Postal Code" sap:semantics="zip"/>
<Property Name="Street" Type="Edm.String" MaxLength="60" sap:label="Street" sap:semantics="street"/>
<Property Name="Building" Type="Edm.String" MaxLength="10" sap:label="Building"/>
<Property Name="Country" Type="Edm.String" MaxLength="3" sap:label="Country" sap:semantics="country"/>
<Property Name="AddressType" Type="Edm.String" MaxLength="2" sap:label="Address Type"/>
</ComplexType>
So the above XML definition becomes:
```rust
pub struct Address {
pub addresstype: Option
The metadata for the GWSAMPLE_BASIC
OData service contains the following complex type:
xml
<ComplexType Name="CT_String">
<Property Name="String" Type="Edm.String" Nullable="false" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
</ComplexType>
Allowing for the current situation in which additional attribute values and SAP Annotations are not preserved, this particular type turns out not to be complex at all — its just a String
.
In such cases, fields declared to be of these "simple" complex types (such as CT_String
), are collapsed down to the Rust native type of the single inner property — which in this example is simply a String
.
chrono::naive::NaiveDateTime
type is the best fit for Edm.DateTimeOffset
build.rs
script to allow another app to consume the generated code
OUT_DIR
The Rust tool chain can be installed by following these instructions
bash
$ git clone https://github.com/lighthouse-no/parse-sap-odata
If you wish to see the console output created during a test, cargo
must be passed the flags -- --nocapture
.
```bash 13:49 $ cargo test -- --nocapture Finished test [unoptimized + debuginfo] target(s) in 0.05s Running unittests src/lib.rs (target/debug/deps/parsesapodata-37f19949c8ea98f2)
running 1 test Generating source code from pagebuilderpers.xml Generating source code from sepmragrpost.xml Generating source code from ze2e100sol2srv.xml Generating source code from sgbtntecdsapidsrv.xml Generating source code from interop.xml Generating source code from zsocdssrv.xml Generating source code from epmrefappspoapvsrv.xml Generating source code from zdevelopercenter.xml Generating source code from sepmraprodman.xml Generating source code from sepmrasoman.xml Generating source code from ztestcdswithparamsrv.xml Generating source code from pagebuildercust.xml Generating source code from epmrefappsshopsrv.xml Generating source code from zepmrefappspoapvsrv.xml Generating source code from transport.xml Generating source code from sepmrapoapv.xml Generating source code from pagebuilderconf.xml Generating source code from rmtsampleflight.xml Generating source code from gwsamplebasic.xml Generating source code from zagencycdssrv.xml Generating source code from zpdcdssrv.xml Generating source code from epmrefappsprodmansrv.xml Generating source code from sepmrapoman.xml Generating source code from catalogservicev0002.xml Generating source code from catalogservice.xml Generating source code from gwdemo.xml Generating source code from sepmraovw.xml Generating source code from errorlogsrv.xml Generating source code from sgbtntecdsapisrv.xml Generating source code from zrfc1srv.xml Generating source code from sepmrashop.xml test unittests::tests::gensrc_all ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 3 filtered out; finished in 2.26s ```
For each input XML file, a corresponding .rs
file will appear in the ./gen
directory.