Skip to content

Commit 2a5dd44

Browse files
committed
rofl-scheduler: Simplify offers to be Vec<RawOfferConfig
1 parent 6ccb3e8 commit 2a5dd44

1 file changed

Lines changed: 84 additions & 82 deletions

File tree

rofl-scheduler/src/config.rs

Lines changed: 84 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,21 @@ use oasis_runtime_sdk_rofl_market as market;
2020
const ROFL_SCHEDULER_CONFIG_KEY: &str = "rofl_scheduler";
2121

2222
/// Raw per-offer configuration as serialized.
23-
#[derive(Clone, Debug, Default, cbor::Decode)]
23+
///
24+
/// Each element of the `offers` sequence is either a plain string (old format, no overrides) or
25+
/// a map with an `id` field and optional per-offer config overrides:
26+
///
27+
/// ```yaml
28+
/// offers:
29+
/// - public # plain string — global defaults apply
30+
/// - id: internal # map entry — per-offer overrides
31+
/// allowed_creators:
32+
/// - "oasis1..."
33+
/// ```
34+
#[derive(Clone, Debug, Default)]
2435
pub struct RawOfferConfig {
36+
/// Offer identifier (value of the `net.oasis.scheduler.offer` metadata key).
37+
pub id: String,
2538
/// Allowed instance creator addresses for this offer.
2639
///
2740
/// When set, overrides the global `allowed_creators`. An empty list means all creators are
@@ -34,60 +47,52 @@ pub struct RawOfferConfig {
3447
pub allowed_artifacts: Option<BTreeMap<String, Vec<String>>>,
3548
}
3649

37-
/// A map entry with an explicit `id` field and optional per-offer config overrides.
38-
#[derive(Clone, Debug, Default, cbor::Decode)]
39-
struct RawOfferEntry {
40-
pub id: String,
41-
pub allowed_creators: Option<Vec<String>>,
42-
pub allowed_artifacts: Option<BTreeMap<String, Vec<String>>>,
43-
}
44-
45-
/// Backwards-compatible wrapper for the `offers` field in [`RawLocalConfig`].
46-
///
47-
/// Each element of the sequence is either a plain string (old format, no overrides) or a
48-
/// map with an `id` field and optional per-offer config overrides:
49-
///
50-
/// ```yaml
51-
/// offers:
52-
/// - playground_short # plain string — global defaults apply
53-
/// - id: oasis_internal # map entry — per-offer overrides
54-
/// allowed_creators:
55-
/// - "oasis1..."
56-
/// ```
57-
#[derive(Clone, Debug, Default)]
58-
pub(crate) struct RawOffersField(BTreeMap<String, RawOfferConfig>);
59-
60-
impl cbor::Decode for RawOffersField {
50+
impl cbor::Decode for RawOfferConfig {
6151
fn try_default() -> Result<Self, cbor::DecodeError> {
62-
Ok(Self(BTreeMap::new()))
52+
Ok(Self::default())
6353
}
6454

6555
fn try_from_cbor_value(value: cbor::Value) -> Result<Self, cbor::DecodeError> {
66-
let cbor::Value::Array(items) = value else {
67-
return Err(cbor::DecodeError::UnexpectedType);
68-
};
69-
let mut map = BTreeMap::new();
70-
for item in items {
71-
match item {
72-
// Plain string: "offer-name" → empty config (global defaults apply).
73-
cbor::Value::TextString(key) => {
74-
map.insert(key, RawOfferConfig::default());
56+
match value {
57+
// Plain string: the offer name with no overrides (global defaults apply).
58+
cbor::Value::TextString(id) => Ok(Self {
59+
id,
60+
allowed_creators: None,
61+
allowed_artifacts: None,
62+
}),
63+
// Map with an `id` field and optional overrides.
64+
cbor::Value::Map(entries) => {
65+
let mut id = None;
66+
let mut allowed_creators = None;
67+
let mut allowed_artifacts = None;
68+
for (k, v) in entries {
69+
let cbor::Value::TextString(key) = k else {
70+
return Err(cbor::DecodeError::UnexpectedType);
71+
};
72+
match key.as_str() {
73+
"id" => {
74+
id = Some(<String as cbor::Decode>::try_from_cbor_value(v)?);
75+
}
76+
"allowed_creators" => {
77+
allowed_creators =
78+
Some(<Vec<String> as cbor::Decode>::try_from_cbor_value(v)?);
79+
}
80+
"allowed_artifacts" => {
81+
allowed_artifacts = Some(
82+
<BTreeMap<String, Vec<String>> as cbor::Decode>::try_from_cbor_value(v)?,
83+
);
84+
}
85+
_ => {}
86+
}
7587
}
76-
// Map with an `id` field and optional overrides.
77-
cbor::Value::Map(_) => {
78-
let entry = RawOfferEntry::try_from_cbor_value(item)?;
79-
map.insert(
80-
entry.id,
81-
RawOfferConfig {
82-
allowed_creators: entry.allowed_creators,
83-
allowed_artifacts: entry.allowed_artifacts,
84-
},
85-
);
86-
}
87-
_ => return Err(cbor::DecodeError::UnexpectedType),
88+
Ok(Self {
89+
id: id.ok_or(cbor::DecodeError::MissingField)?,
90+
allowed_creators,
91+
allowed_artifacts,
92+
})
8893
}
94+
_ => Err(cbor::DecodeError::UnexpectedType),
8995
}
90-
Ok(Self(map))
9196
}
9297
}
9398

@@ -98,11 +103,9 @@ pub struct RawLocalConfig {
98103
pub provider_address: String,
99104
/// Offers that the scheduler should accept. If no offers are configured, all are accepted.
100105
///
101-
/// Each key is the value of the `net.oasis.scheduler.offer` metadata key. The value is an
102-
/// optional per-offer configuration that overrides the global defaults.
103-
///
104-
/// Accepts the legacy plain-string array form (backwards compatible) or the new map-entry form.
105-
pub(crate) offers: RawOffersField,
106+
/// Each entry is either a plain string (legacy form, no overrides) or a map with an `id`
107+
/// field and optional per-offer config overrides.
108+
pub offers: Vec<RawOfferConfig>,
106109
/// Allowed artifact hashes.
107110
///
108111
/// Key is the artifact kind and value is a list of artifact SHA256 hashes. If a key doesn't
@@ -291,9 +294,9 @@ impl LocalConfig {
291294

292295
let offers = cfg
293296
.offers
294-
.0
295297
.into_iter()
296-
.map(|(key, raw_cfg)| -> Result<(String, OfferConfig)> {
298+
.map(|raw_cfg| -> Result<(String, OfferConfig)> {
299+
let key = raw_cfg.id;
297300
let offer_creators = raw_cfg
298301
.allowed_creators
299302
.map(|creators| {
@@ -376,7 +379,7 @@ impl LocalConfig {
376379
.unwrap_or(&self.allowed_artifacts);
377380

378381
match artifacts.get(kind) {
379-
None => Ok(()), // all artifacts of this kind are allowed
382+
None => Ok(()), // All artifacts of this kind are allowed.
380383
Some(allowed_hashes) => {
381384
if !allowed_hashes.contains(hash) {
382385
Err(anyhow!("{kind} artifact not allowed"))
@@ -408,38 +411,37 @@ impl LocalConfig {
408411

409412
#[cfg(test)]
410413
mod test {
414+
use oasis_runtime_sdk::testing;
415+
411416
use super::*;
412417

413-
fn make_raw(offers_cbor: cbor::Value) -> RawLocalConfig {
414-
let mut raw = RawLocalConfig::default();
415-
raw.offers = cbor::Decode::try_from_cbor_value(offers_cbor).unwrap();
416-
raw
418+
fn decode_offers(cbor: cbor::Value) -> Vec<RawOfferConfig> {
419+
<Vec<RawOfferConfig> as cbor::Decode>::try_from_cbor_value(cbor).unwrap()
417420
}
418421

419422
#[test]
420423
fn test_offers_legacy_array_format() {
421-
// Old format: plain array of strings. Each becomes a key with empty OfferConfig.
422-
let raw = make_raw(cbor::Value::Array(vec![
423-
cbor::Value::TextString("playground_short".into()),
424-
cbor::Value::TextString("playground_short_sgx".into()),
424+
// Old format: plain array of strings. Each becomes a RawOfferConfig with no overrides.
425+
let offers = decode_offers(cbor::Value::Array(vec![
426+
cbor::Value::TextString("small".into()),
427+
cbor::Value::TextString("large".into()),
425428
]));
426-
assert!(raw.offers.0.contains_key("playground_short"));
427-
assert!(raw.offers.0.contains_key("playground_short_sgx"));
428-
assert_eq!(raw.offers.0.len(), 2);
429-
// No per-offer overrides — both fields are None (global fallback applies).
430-
assert!(raw.offers.0["playground_short"].allowed_creators.is_none());
429+
assert_eq!(offers.len(), 2);
430+
assert_eq!(offers[0].id, "small");
431+
assert!(offers[0].allowed_creators.is_none());
432+
assert_eq!(offers[1].id, "large");
431433
}
432434

433435
#[test]
434436
fn test_offers_mixed_format() {
435437
// Mixed: plain strings alongside a map entry with an explicit `id` field and overrides.
436-
let raw = make_raw(cbor::Value::Array(vec![
437-
cbor::Value::TextString("playground_short".into()),
438-
cbor::Value::TextString("playground_short_sgx".into()),
438+
let offers = decode_offers(cbor::Value::Array(vec![
439+
cbor::Value::TextString("small".into()),
440+
cbor::Value::TextString("large".into()),
439441
cbor::Value::Map(vec![
440442
(
441443
cbor::Value::TextString("id".into()),
442-
cbor::Value::TextString("oasis_internal".into()),
444+
cbor::Value::TextString("internal".into()),
443445
),
444446
(
445447
cbor::Value::TextString("allowed_creators".into()),
@@ -449,10 +451,12 @@ mod test {
449451
),
450452
]),
451453
]));
452-
assert!(raw.offers.0.contains_key("playground_short"));
453-
assert!(raw.offers.0["playground_short"].allowed_creators.is_none());
454-
assert!(raw.offers.0.contains_key("oasis_internal"));
455-
assert!(raw.offers.0["oasis_internal"].allowed_creators.is_some());
454+
assert_eq!(offers[0].id, "small");
455+
assert!(offers[0].allowed_creators.is_none());
456+
assert_eq!(offers[1].id, "large");
457+
assert!(offers[1].allowed_creators.is_none());
458+
assert_eq!(offers[2].id, "internal");
459+
assert!(offers[2].allowed_creators.is_some());
456460
}
457461

458462
#[test]
@@ -463,16 +467,14 @@ mod test {
463467
allowed_creators: BTreeSet::new(), // global: allow all
464468
..Default::default()
465469
};
466-
let addr = Address::from_bech32("oasis1qp0cnmkjl22gky6p7q0tgkwmsc6g4c5er6x0hsk7").unwrap();
470+
let addr = testing::keys::alice::address();
467471
assert!(cfg.is_creator_allowed("public", &addr));
468472
}
469473

470474
#[test]
471475
fn test_is_creator_allowed_per_offer_override() {
472-
let allowed =
473-
Address::from_bech32("oasis1qp0cnmkjl22gky6p7q0tgkwmsc6g4c5er6x0hsk7").unwrap();
474-
let blocked =
475-
Address::from_bech32("oasis1qrad7s7nqm4gvyzr8yt48jkrjxuqc6d7pvjm4ze").unwrap();
476+
let allowed = testing::keys::alice::address();
477+
let blocked = testing::keys::bob::address();
476478

477479
let cfg = LocalConfig {
478480
offers: BTreeMap::from([(

0 commit comments

Comments
 (0)